├── .gitignore ├── .gitmodules ├── .idea ├── .name ├── codeStyleSettings.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE.md ├── README.md ├── ServalChat.iml ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── .gitignore │ ├── AndroidManifest.xml │ ├── java │ │ └── org │ │ │ └── servalproject │ │ │ ├── mid │ │ │ ├── AbstractFutureList.java │ │ │ ├── AbstractGrowingList.java │ │ │ ├── ActivityList.java │ │ │ ├── CallbackHandler.java │ │ │ ├── Config.java │ │ │ ├── FeedList.java │ │ │ ├── IObservableList.java │ │ │ ├── IObserverSet.java │ │ │ ├── Identities.java │ │ │ ├── Identity.java │ │ │ ├── IdentityFeed.java │ │ │ ├── Interface.java │ │ │ ├── KnownPeers.java │ │ │ ├── ListObserver.java │ │ │ ├── ListObserverSet.java │ │ │ ├── MessageFeed.java │ │ │ ├── MessageList.java │ │ │ ├── Messaging.java │ │ │ ├── Observer.java │ │ │ ├── ObserverProxy.java │ │ │ ├── ObserverSet.java │ │ │ ├── Peer.java │ │ │ ├── Rhizome.java │ │ │ ├── SelfUpdater.java │ │ │ ├── Serval.java │ │ │ ├── Server.java │ │ │ └── networking │ │ │ │ ├── AbstractListObserver.java │ │ │ │ ├── FlightModeObserver.java │ │ │ │ ├── Hotspot.java │ │ │ │ ├── NetworkInfo.java │ │ │ │ ├── Networks.java │ │ │ │ ├── WifiAware.java │ │ │ │ ├── WifiClient.java │ │ │ │ ├── WifiHotspotChanges.java │ │ │ │ ├── WifiNetworkChanges.java │ │ │ │ └── bluetooth │ │ │ │ ├── BlueToothControl.java │ │ │ │ ├── BlueToothInfo.java │ │ │ │ ├── BluetoothNetworkChanges.java │ │ │ │ ├── Connector.java │ │ │ │ ├── PeerReader.java │ │ │ │ ├── PeerState.java │ │ │ │ ├── Scanner.java │ │ │ │ └── SocketListener.java │ │ │ └── servalchat │ │ │ ├── App.java │ │ │ ├── CustomFileProvider.java │ │ │ ├── ForegroundService.java │ │ │ ├── Notifications.java │ │ │ ├── SampleData.java │ │ │ ├── feeds │ │ │ ├── ActivityAdapter.java │ │ │ ├── BlockList.java │ │ │ ├── Contacts.java │ │ │ ├── FeedAdapter.java │ │ │ ├── FeedListAdapter.java │ │ │ ├── MyFeed.java │ │ │ ├── MyFeedPresenter.java │ │ │ ├── PeerFeed.java │ │ │ ├── PeerFeedPresenter.java │ │ │ ├── PublicFeedsList.java │ │ │ └── PublicFeedsPresenter.java │ │ │ ├── identity │ │ │ ├── ConversationList.java │ │ │ ├── IdentityDetails.java │ │ │ ├── IdentityDetailsPresenter.java │ │ │ └── IdentityList.java │ │ │ ├── navigation │ │ │ ├── CountTitle.java │ │ │ ├── HistoryItem.java │ │ │ ├── IContainerView.java │ │ │ ├── IHaveMenu.java │ │ │ ├── ILifecycle.java │ │ │ ├── INavigate.java │ │ │ ├── INavigatorHost.java │ │ │ ├── IOnBack.java │ │ │ ├── IRootContainer.java │ │ │ ├── Id1.java │ │ │ ├── Id2.java │ │ │ ├── Id3.java │ │ │ ├── Id4.java │ │ │ ├── MainActivity.java │ │ │ ├── NavHistory.java │ │ │ ├── NavPageAdapter.java │ │ │ ├── NavTabStrip.java │ │ │ ├── NavTitle.java │ │ │ ├── Navigation.java │ │ │ ├── RootAppbar.java │ │ │ ├── RootSidebar.java │ │ │ └── ViewState.java │ │ │ ├── network │ │ │ └── NetworkList.java │ │ │ ├── peer │ │ │ ├── PeerDetails.java │ │ │ ├── PeerHolder.java │ │ │ ├── PeerList.java │ │ │ ├── PeerMap.java │ │ │ ├── PrivateMessaging.java │ │ │ └── PrivateMessagingPresenter.java │ │ │ └── views │ │ │ ├── BackgroundWorker.java │ │ │ ├── BasicViewHolder.java │ │ │ ├── DisplayError.java │ │ │ ├── FutureList.java │ │ │ ├── Identicon.java │ │ │ ├── MessageViewHolder.java │ │ │ ├── ObservedRecyclerView.java │ │ │ ├── Presenter.java │ │ │ ├── PresenterFactory.java │ │ │ ├── RecyclerHelper.java │ │ │ ├── ScrollingAdapter.java │ │ │ ├── SimpleRecyclerView.java │ │ │ ├── SpinnerViewHolder.java │ │ │ ├── TimestampView.java │ │ │ └── UIObserver.java │ ├── jni │ │ ├── Android.mk │ │ ├── Application.mk │ │ └── chat_features.c │ └── res │ │ ├── drawable │ │ ├── ic_add_account.xml │ │ ├── ic_add_contact.xml │ │ ├── ic_block_contact.xml │ │ ├── ic_contacts.xml │ │ ├── ic_remove_contact.xml │ │ ├── progress.xml │ │ └── side_nav_bar.xml │ │ ├── layout │ │ ├── ack_message.xml │ │ ├── activity_message.xml │ │ ├── block_list.xml │ │ ├── contacts.xml │ │ ├── conversation_item.xml │ │ ├── conversation_list.xml │ │ ├── error.xml │ │ ├── feed.xml │ │ ├── feed_list.xml │ │ ├── feed_message.xml │ │ ├── identity.xml │ │ ├── identity_details.xml │ │ ├── identity_list.xml │ │ ├── main.xml │ │ ├── main_sidebar.xml │ │ ├── main_tabs.xml │ │ ├── message_ack.xml │ │ ├── message_list.xml │ │ ├── my_feed.xml │ │ ├── my_message.xml │ │ ├── nav_header.xml │ │ ├── network.xml │ │ ├── networking.xml │ │ ├── peer.xml │ │ ├── peer_details.xml │ │ ├── peer_feed.xml │ │ ├── peer_list.xml │ │ ├── peer_map.xml │ │ ├── placeholder.xml │ │ ├── progress.xml │ │ └── their_message.xml │ │ ├── mipmap-hdpi │ │ └── serval_head.png │ │ ├── mipmap-mdpi │ │ └── serval_head.png │ │ ├── mipmap-xhdpi │ │ └── serval_head.png │ │ ├── mipmap-xxhdpi │ │ └── serval_head.png │ │ ├── mipmap-xxxhdpi │ │ └── serval_head.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── network_security_config.xml │ └── test │ └── java │ └── org │ └── servalproject │ └── servalchat │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries/ 5 | /.idea/caches/ 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "app/src/main/jni/serval-dna"] 2 | path = app/src/main/jni/serval-dna 3 | url = ../serval-dna.git 4 | [submodule "app/src/main/jni/libsodium"] 5 | path = app/src/main/jni/libsodium 6 | url = ../libsodium.git 7 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ServalChat -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Serval Chat, Copyright (C) 2016 Flinders University 2 | 3 | This program is free software; you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License version 3 as 5 | published by the Free Software Foundation. 6 | See http://www.gnu.org/licenses/ 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | -------------------------------------------------------------------------------- /ServalChat.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.apk 3 | .externalNativeBuild 4 | .cxx 5 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/etc/android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/.gitignore: -------------------------------------------------------------------------------- 1 | obj 2 | libs 3 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/AbstractFutureList.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import org.servalproject.json.JsonParser; 4 | import org.servalproject.servaldna.HttpJsonSerialiser; 5 | import org.servalproject.servaldna.ServalDInterfaceException; 6 | 7 | import java.io.IOException; 8 | 9 | /** 10 | * Created by jeremy on 30/01/17. 11 | */ 12 | 13 | public abstract class AbstractFutureList 14 | extends AbstractGrowingList{ 15 | private final ListObserverSet observeFuture; 16 | private HttpJsonSerialiser futureList; 17 | private boolean polling = false; 18 | protected T last; 19 | 20 | protected AbstractFutureList(Serval serval) { 21 | super(serval); 22 | this.observeFuture = new ListObserverSet<>(serval); 23 | } 24 | protected void start() { 25 | if (polling || !observeFuture.hasObservers()) 26 | return; 27 | polling = true; 28 | serval.runOnThreadPool(readFuture); 29 | } 30 | 31 | public void observe(ListObserver observer) { 32 | observeFuture.add(observer); 33 | start(); 34 | } 35 | 36 | @Override 37 | public void stopObserving(ListObserver observer) { 38 | observeFuture.remove(observer); 39 | if (!observeFuture.hasObservers()) { 40 | polling = false; 41 | closeFuture(); 42 | } 43 | } 44 | 45 | protected abstract HttpJsonSerialiser openFuture() throws ServalDInterfaceException, E, IOException, JsonParser.JsonParseException; 46 | 47 | @Override 48 | protected void addingPastItem(T item) { 49 | if (last == null) { 50 | last = item; 51 | start(); 52 | } 53 | super.addingPastItem(item); 54 | } 55 | 56 | @SuppressWarnings("unchecked") 57 | protected void addingFutureItem(T item) { 58 | boolean isLatest = true; 59 | 60 | if (last != null && item instanceof Comparable) 61 | isLatest = ((Comparable)item).compareTo(last) <0; 62 | if (isLatest) 63 | last = item; 64 | 65 | observeFuture.onAdd(item); 66 | } 67 | 68 | private Runnable readFuture = new Runnable() { 69 | @Override 70 | public void run() { 71 | while (polling) { 72 | try { 73 | HttpJsonSerialiser list = futureList = openFuture(); 74 | if (list != null) { 75 | T item; 76 | while (polling && (item = list.next()) != null) { 77 | addingFutureItem(item); 78 | } 79 | // on graceful close from the server, restart 80 | list.close(); 81 | } 82 | if (futureList == list) 83 | futureList = null; 84 | } catch (IOException | 85 | JsonParser.JsonParseException e) { 86 | // ignore if we caused this deliberately in another thread. 87 | if (polling) 88 | throw new IllegalStateException(e); 89 | } catch (RuntimeException e) { 90 | throw e; 91 | } catch (Exception e) { 92 | throw new IllegalStateException(e); 93 | } 94 | } 95 | } 96 | }; 97 | 98 | private void closeFuture(){ 99 | HttpJsonSerialiser list = futureList; 100 | if (list != null) { 101 | try { 102 | list.close(); 103 | } catch (IOException e) { 104 | } 105 | futureList = null; 106 | } 107 | } 108 | 109 | @Override 110 | public void close() { 111 | super.close(); 112 | polling = false; 113 | closeFuture(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/AbstractGrowingList.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import org.servalproject.json.JsonParser; 4 | import org.servalproject.servaldna.HttpJsonSerialiser; 5 | import org.servalproject.servaldna.ServalDInterfaceException; 6 | 7 | import java.io.IOException; 8 | 9 | /** 10 | * Created by jeremy on 11/10/16. 11 | */ 12 | public abstract class AbstractGrowingList 13 | implements IObservableList{ 14 | protected final Serval serval; 15 | protected boolean hasMore = true; 16 | private boolean closed = false; 17 | private HttpJsonSerialiser pastList; 18 | 19 | protected AbstractGrowingList(Serval serval) { 20 | this.serval = serval; 21 | } 22 | 23 | protected abstract HttpJsonSerialiser openPast() throws ServalDInterfaceException, E, IOException, JsonParser.JsonParseException; 24 | 25 | protected void addingPastItem(T item) { 26 | } 27 | 28 | @Override 29 | public T next() throws ServalDInterfaceException, E, IOException, JsonParser.JsonParseException { 30 | if (!hasMore) 31 | return null; 32 | if (pastList == null) 33 | pastList = openPast(); 34 | 35 | T item = null; 36 | if (pastList != null) 37 | item = pastList.next(); 38 | if (item == null) { 39 | hasMore = false; 40 | if (pastList != null) 41 | pastList.close(); 42 | pastList = null; 43 | } 44 | addingPastItem(item); 45 | return item; 46 | } 47 | 48 | @Override 49 | public void close() { 50 | hasMore = false; 51 | closed = true; 52 | if (pastList != null) { 53 | try { 54 | pastList.close(); 55 | } catch (IOException e) { 56 | } 57 | pastList = null; 58 | } 59 | } 60 | 61 | @Override 62 | public void observe(ListObserver observer) { 63 | // NOOP 64 | } 65 | 66 | @Override 67 | public void stopObserving(ListObserver observer) { 68 | // NOOP 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/ActivityList.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import org.servalproject.servaldna.HttpJsonSerialiser; 4 | import org.servalproject.servaldna.ServalDInterfaceException; 5 | import org.servalproject.servaldna.meshmb.MeshMBActivityMessage; 6 | 7 | import java.io.IOException; 8 | 9 | /** 10 | * Created by jeremy on 30/01/17. 11 | */ 12 | 13 | public class ActivityList extends AbstractFutureList { 14 | private final Identity identity; 15 | 16 | ActivityList(Serval serval, Identity identity) { 17 | super(serval); 18 | this.identity = identity; 19 | } 20 | 21 | @Override 22 | protected void start() { 23 | if (last == null && hasMore) 24 | return; 25 | super.start(); 26 | } 27 | 28 | private void updatePeer(MeshMBActivityMessage item){ 29 | Peer p = serval.knownPeers.getPeer(item.subscriber); 30 | p.updateFeedName(item.name); 31 | } 32 | 33 | @Override 34 | protected void addingPastItem(MeshMBActivityMessage item) { 35 | if (item!=null) 36 | updatePeer(item); 37 | super.addingPastItem(item); 38 | } 39 | 40 | @Override 41 | protected void addingFutureItem(MeshMBActivityMessage item) { 42 | updatePeer(item); 43 | super.addingFutureItem(item); 44 | } 45 | 46 | @Override 47 | protected HttpJsonSerialiser openPast() throws ServalDInterfaceException, IOException, IOException { 48 | return serval.getResultClient().meshmbActivity(identity.subscriber); 49 | } 50 | 51 | @Override 52 | protected HttpJsonSerialiser openFuture() throws ServalDInterfaceException, IOException, IOException { 53 | return serval.getResultClient().meshmbActivity(identity.subscriber, last == null ? "" : last.token); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/CallbackHandler.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.os.Message; 6 | 7 | /** 8 | * Created by jeremy on 11/05/16. 9 | */ 10 | public class CallbackHandler extends Handler { 11 | 12 | CallbackHandler(Looper looper) { 13 | super(looper); 14 | } 15 | 16 | @Override 17 | public void handleMessage(Message msg) { 18 | Object c = msg.obj; 19 | if (c != null){ 20 | if (c instanceof CallbackMessage) { 21 | CallbackMessage m = (CallbackMessage) c; 22 | m.handleMessage(msg); 23 | return; 24 | }else if(c instanceof Callback){ 25 | ((Callback)c).handleMessage(msg); 26 | return; 27 | } 28 | } 29 | super.dispatchMessage(msg); 30 | } 31 | 32 | public void sendEmptyMessage(Callback callback, int what){ 33 | Message msg = obtainMessage(what, callback); 34 | sendMessage(msg); 35 | } 36 | 37 | public void sendMessage(MessageHandler h, T obj, int what) { 38 | if (isOnThread()) { 39 | // call immediately if already in the right thread 40 | h.handleMessage(obj, what); 41 | } else { 42 | // TODO reuse CallbackMessage instances? 43 | CallbackMessage m = new CallbackMessage<>(h, obj); 44 | Message msg = obtainMessage(what, m); 45 | sendMessage(msg); 46 | } 47 | } 48 | 49 | public boolean isOnThread() { 50 | return Thread.currentThread() == this.getLooper().getThread(); 51 | } 52 | 53 | private class CallbackMessage { 54 | final MessageHandler handler; 55 | final T obj; 56 | 57 | CallbackMessage(MessageHandler handler, T obj) { 58 | this.handler = handler; 59 | this.obj = obj; 60 | } 61 | 62 | void handleMessage(Message msg) { 63 | handler.handleMessage(obj, msg.what); 64 | } 65 | } 66 | 67 | public interface MessageHandler { 68 | void handleMessage(T obj, int what); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/Config.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import org.servalproject.servaldna.ServalDCommand; 4 | import org.servalproject.servaldna.ServalDFailureException; 5 | 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * Created by jeremy on 3/05/16. 13 | */ 14 | 15 | public class Config { 16 | 17 | private Map values = null; 18 | private Map pending = new HashMap<>(); 19 | private static final String deleteFlag = "deleteme"; 20 | 21 | private static final String TAG = "Config"; 22 | 23 | void set(String name, String value) { 24 | pending.put(name, value); 25 | } 26 | 27 | String get(String name) { 28 | String value = null; 29 | if (pending.containsKey(name)) { 30 | value = pending.get(name); 31 | // don't use .equals() here 32 | if (value == deleteFlag) 33 | value = null; 34 | } else if (values != null) 35 | value = values.get(name); 36 | return value; 37 | } 38 | 39 | void delete(String name) { 40 | pending.put(name, deleteFlag); 41 | } 42 | 43 | void load() throws ServalDFailureException { 44 | ServalDCommand.ConfigItems items = ServalDCommand.getConfig(); 45 | values = items.values; 46 | } 47 | 48 | void reset(){ 49 | for(String key : values.keySet()) 50 | delete(key); 51 | } 52 | 53 | public void sync() throws ServalDFailureException { 54 | if (pending.isEmpty()) 55 | return; 56 | 57 | List changes = new ArrayList<>(); 58 | for (String key : pending.keySet()) { 59 | String value = pending.get(key); 60 | // don't use .equals() here 61 | if (value == deleteFlag) { 62 | changes.add("del"); 63 | changes.add(key); 64 | } else { 65 | changes.add("set"); 66 | changes.add(key); 67 | changes.add(value); 68 | } 69 | } 70 | changes.add("sync"); 71 | ServalDCommand.configActions(changes.toArray(new Object[changes.size()])); 72 | load(); 73 | pending.clear(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/FeedList.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import org.servalproject.servaldna.HttpJsonSerialiser; 4 | import org.servalproject.servaldna.ServalDInterfaceException; 5 | import org.servalproject.servaldna.Subscriber; 6 | import org.servalproject.servaldna.meshmb.MeshMBCommon; 7 | import org.servalproject.servaldna.rhizome.RhizomeBundleList; 8 | import org.servalproject.servaldna.rhizome.RhizomeListBundle; 9 | 10 | import java.io.IOException; 11 | 12 | /** 13 | * Created by jeremy on 11/10/16. 14 | */ 15 | public class FeedList extends AbstractFutureList { 16 | private static final String TAG = "FeedList"; 17 | public final String search; 18 | 19 | public FeedList(Serval serval, String search) { 20 | super(serval); 21 | this.search = search; 22 | } 23 | 24 | @Override 25 | protected void start() { 26 | if (last == null && hasMore) 27 | return; 28 | super.start(); 29 | } 30 | 31 | @Override 32 | protected HttpJsonSerialiser openPast() throws ServalDInterfaceException, IOException { 33 | RhizomeBundleList list = new RhizomeBundleList(serval.getResultClient()); 34 | list.setServiceFilter(MeshMBCommon.SERVICE); 35 | if (search!=null) 36 | list.setNameFilter(search); 37 | list.connect(); 38 | return list; 39 | } 40 | 41 | @Override 42 | protected HttpJsonSerialiser openFuture() throws ServalDInterfaceException, IOException { 43 | RhizomeBundleList list = new RhizomeBundleList(serval.getResultClient(), last == null? "" : last.token); 44 | list.setServiceFilter(MeshMBCommon.SERVICE); 45 | if (search!=null && !"".equals(search)) 46 | list.setNameFilter(search); 47 | list.connect(); 48 | return list; 49 | } 50 | 51 | private void updatePeer(RhizomeListBundle item) { 52 | // TODO verify that the sender and id are for the same identity! 53 | // for now we can assume this, but we might break this rule in a future version 54 | if (item.author == null && item.manifest.sender == null) 55 | return; 56 | Subscriber subscriber = new Subscriber( 57 | item.author != null ? item.author : item.manifest.sender, 58 | item.manifest.id, true); 59 | Peer p = serval.knownPeers.getPeer(subscriber); 60 | p.updateFeedName(item.manifest.name); 61 | } 62 | 63 | @Override 64 | protected void addingFutureItem(RhizomeListBundle item) { 65 | updatePeer(item); 66 | super.addingFutureItem(item); 67 | } 68 | 69 | @Override 70 | protected void addingPastItem(RhizomeListBundle item) { 71 | if (item != null) 72 | updatePeer(item); 73 | super.addingPastItem(item); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/IObservableList.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import org.servalproject.json.JsonParser; 4 | import org.servalproject.servaldna.ServalDInterfaceException; 5 | 6 | import java.io.IOException; 7 | 8 | /** 9 | * Created by jeremy on 8/08/16. 10 | */ 11 | public interface IObservableList{ 12 | T next() throws ServalDInterfaceException, E, IOException, JsonParser.JsonParseException; 13 | void close(); 14 | void observe(ListObserver observer); 15 | void stopObserving(ListObserver observer); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/IObserverSet.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | public interface IObserverSet { 4 | void addUI(Observer observer); 5 | 6 | void removeUI(Observer observer); 7 | 8 | void addBackground(Observer observer); 9 | 10 | void removeBackground(Observer observer); 11 | 12 | T getObj(); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/Identities.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import org.servalproject.json.JsonParser; 4 | import org.servalproject.servaldna.ServalDInterfaceException; 5 | import org.servalproject.servaldna.SigningKey; 6 | import org.servalproject.servaldna.Subscriber; 7 | import org.servalproject.servaldna.SubscriberId; 8 | import org.servalproject.servaldna.keyring.KeyringIdentity; 9 | import org.servalproject.servaldna.keyring.KeyringIdentityList; 10 | 11 | import java.io.IOException; 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | /** 18 | * Created by jeremy on 6/06/16. 19 | */ 20 | public class Identities { 21 | private static final String TAG = "Identities"; 22 | private final Serval serval; 23 | private boolean loaded = false; 24 | public final ListObserverSet listObservers; 25 | private final List identityList = new ArrayList<>(); 26 | private final Map identities = new HashMap<>(); 27 | private Identity selected; 28 | 29 | Identities(Serval serval) { 30 | this.serval = serval; 31 | listObservers = new ListObserverSet<>(serval); 32 | } 33 | 34 | void onStart() { 35 | enterPin(null); 36 | } 37 | 38 | private Identity addId(KeyringIdentity id) { 39 | Identity i = identities.get(id.subscriber); 40 | if (i == null) { 41 | i = new Identity(serval, id.subscriber); 42 | identities.put(id.subscriber, i); 43 | identityList.add(i); 44 | if (selected == null) 45 | selected = i; 46 | i.update(id); 47 | listObservers.onAdd(i); 48 | } else { 49 | i.update(id); 50 | listObservers.onUpdate(i); 51 | } 52 | return i; 53 | } 54 | 55 | public List getIdentities() { 56 | return identityList; 57 | } 58 | 59 | public boolean isLoaded() { 60 | return loaded; 61 | } 62 | 63 | public Identity getIdentity(SigningKey key) { 64 | for (Identity id : identityList) { 65 | if (id.subscriber.signingKey.equals(key)) 66 | return id; 67 | } 68 | return null; 69 | } 70 | 71 | public void updateIdentity(final Identity id, final String did, final String name, final String pin) throws ServalDInterfaceException, IOException { 72 | addId(serval.getResultClient().keyringSetDidName(id.subscriber, did, name, pin)); 73 | } 74 | 75 | public Identity addIdentity(final String did, final String name, final String pin) throws ServalDInterfaceException, IOException { 76 | return addId(serval.getResultClient().keyringAdd(did, name, pin)); 77 | } 78 | 79 | void enterPin(final String pin) { 80 | serval.runOnThreadPool(new Runnable() { 81 | @Override 82 | public void run() { 83 | try { 84 | KeyringIdentityList list = serval.getResultClient().keyringListIdentities(pin); 85 | KeyringIdentity id = null; 86 | while ((id = list.next()) != null) 87 | addId(id); 88 | if (pin == null) 89 | loaded = true; 90 | listObservers.onReset(); 91 | } catch (ServalDInterfaceException | 92 | JsonParser.JsonParseException | 93 | IOException e) { 94 | throw new IllegalStateException(e); 95 | } 96 | } 97 | }); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/Identity.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Canvas; 5 | 6 | import org.servalproject.servalchat.views.Identicon; 7 | import org.servalproject.servaldna.ServalDInterfaceException; 8 | import org.servalproject.servaldna.Subscriber; 9 | import org.servalproject.servaldna.keyring.KeyringIdentity; 10 | import org.servalproject.servaldna.meshmb.MeshMBCommon; 11 | 12 | import java.io.IOException; 13 | 14 | /** 15 | * Created by jeremy on 6/06/16. 16 | */ 17 | public class Identity { 18 | private final Serval serval; 19 | public final Subscriber subscriber; 20 | public final ObserverSet observers; 21 | private KeyringIdentity identity; 22 | private Identicon icon; 23 | 24 | private static long nextId = 0; 25 | private final long id; 26 | public final Messaging messaging; 27 | 28 | public Identity(Serval serval, Subscriber subscriber) { 29 | this.serval = serval; 30 | this.subscriber = subscriber; 31 | id = nextId++; 32 | if (serval == null) { 33 | // dummy object for ui design 34 | messaging = null; 35 | observers = null; 36 | } else { 37 | observers = new ObserverSet<>(serval, this); 38 | this.messaging = new Messaging(serval, this); 39 | } 40 | } 41 | 42 | public IdentityFeed getFeed() { 43 | return new IdentityFeed(serval, this); 44 | } 45 | 46 | public FeedList getAllFeeds(String search) { 47 | return new FeedList(serval, search); 48 | } 49 | 50 | public ActivityList getActivity() { 51 | return new ActivityList(serval, this); 52 | } 53 | 54 | public void alterSubscription(MeshMBCommon.SubscriptionAction action, Peer peer) throws ServalDInterfaceException, IOException { 55 | serval.getResultClient().meshmbAlterSubscription(subscriber, action, peer.getSubscriber(), peer.getFeedName()); 56 | messaging.subscriptionAltered(action, peer); 57 | } 58 | 59 | public void update(KeyringIdentity id) { 60 | this.identity = id; 61 | if (observers != null) 62 | observers.onUpdate(); 63 | } 64 | 65 | public Identicon getIcon(){ 66 | if (icon == null) 67 | icon = new Identicon(subscriber.signingKey); 68 | return icon; 69 | } 70 | 71 | private Bitmap iconBitmap; 72 | public Bitmap getBitmap(){ 73 | if (iconBitmap == null) { 74 | iconBitmap = Bitmap.createBitmap(48, 48, Bitmap.Config.ARGB_8888); 75 | Canvas canvas = new Canvas(iconBitmap); 76 | getIcon().draw(canvas); 77 | } 78 | return iconBitmap; 79 | } 80 | 81 | 82 | public String getName() { 83 | return identity == null ? null : identity.name; 84 | } 85 | 86 | public String getDid() { 87 | return identity == null ? null : identity.did; 88 | } 89 | 90 | public long getId() { 91 | return id; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/IdentityFeed.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import org.servalproject.servaldna.ServalDInterfaceException; 4 | import org.servalproject.servaldna.meshms.MeshMSException; 5 | 6 | import java.io.IOException; 7 | 8 | /** 9 | * Created by jeremy on 11/10/16. 10 | */ 11 | public class IdentityFeed extends MessageFeed { 12 | public final Identity id; 13 | 14 | IdentityFeed(Serval serval, Identity id) { 15 | super(serval, id.subscriber); 16 | this.id = id; 17 | } 18 | 19 | public void sendMessage(String message) throws ServalDInterfaceException, IOException, MeshMSException { 20 | if (serval.uiHandler.isOnThread()) 21 | throw new IllegalStateException(); 22 | serval.getResultClient().meshmbSendMessage(id.subscriber.signingKey, message); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/Interface.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | /** 4 | * Created by jeremy on 24/10/16. 5 | */ 6 | public class Interface { 7 | public final int id; 8 | public final String name; 9 | 10 | public Interface(int id, String name) { 11 | this.id = id; 12 | this.name = name; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/ListObserver.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | /** 4 | * Created by jeremy on 11/05/16. 5 | */ 6 | public interface ListObserver { 7 | void added(T obj); 8 | 9 | void removed(T obj); 10 | 11 | void updated(T obj); 12 | 13 | void reset(); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/ListObserverSet.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashSet; 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | /** 9 | * Created by jeremy on 11/05/16. 10 | */ 11 | public class ListObserverSet { 12 | private final Set> observers = new HashSet<>(); 13 | private final Set> backgroundObservers = new HashSet<>(); 14 | 15 | private static final int ADD = 1; 16 | private static final int REMOVE = 2; 17 | private static final int UPDATE = 3; 18 | private static final int RESET = 4; 19 | private int generation = 0; 20 | 21 | private final CallbackHandler uiHandler; 22 | private final CallbackHandler backgroundHandler; 23 | 24 | public ListObserverSet(Serval serval) { 25 | this.uiHandler = serval.uiHandler; 26 | this.backgroundHandler = serval.backgroundHandler; 27 | } 28 | 29 | public int add(ListObserver observer) { 30 | observers.add(observer); 31 | return generation; 32 | } 33 | 34 | public int remove(ListObserver observer) { 35 | observers.remove(observer); 36 | return generation; 37 | } 38 | 39 | public int addBackground(ListObserver observer) { 40 | backgroundObservers.add(observer); 41 | return generation; 42 | } 43 | 44 | public int removeBackground(ListObserver observer) { 45 | backgroundObservers.remove(observer); 46 | return generation; 47 | } 48 | 49 | public boolean hasObservers() { 50 | return !observers.isEmpty(); 51 | } 52 | 53 | private void onChange(T t, int what){ 54 | generation++; 55 | if (!observers.isEmpty()) 56 | uiHandler.sendMessage(uiMessageHandler, t, what); 57 | if (!backgroundObservers.isEmpty()) 58 | backgroundHandler.sendMessage(backgroundMessageHandler, t, what); 59 | } 60 | 61 | public void onAdd(T t) { 62 | onChange(t, ADD); 63 | } 64 | 65 | public void onRemove(T t) { 66 | onChange(t, REMOVE); 67 | } 68 | 69 | public void onUpdate(T t) { 70 | onChange(t, UPDATE); 71 | } 72 | 73 | public void onReset() { 74 | onChange(null, RESET); 75 | } 76 | 77 | private CallbackHandler.MessageHandler uiMessageHandler = new CallbackHandler.MessageHandler() { 78 | @Override 79 | public void handleMessage(T obj, int what) { 80 | handle(observers, obj, what); 81 | } 82 | }; 83 | 84 | private CallbackHandler.MessageHandler backgroundMessageHandler = new CallbackHandler.MessageHandler() { 85 | @Override 86 | public void handleMessage(T obj, int what) { 87 | handle(backgroundObservers, obj, what); 88 | } 89 | }; 90 | 91 | private void handle(Set> observers, T obj, int what) { 92 | if (observers.isEmpty()) 93 | return; 94 | // clone the list so handlers can remove while we are iterating 95 | List> notify = new ArrayList<>(observers); 96 | for (ListObserver observer : notify) { 97 | switch (what) { 98 | case ADD: 99 | observer.added(obj); 100 | break; 101 | case REMOVE: 102 | observer.removed(obj); 103 | break; 104 | case UPDATE: 105 | observer.updated(obj); 106 | break; 107 | case RESET: 108 | observer.reset(); 109 | break; 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/MessageFeed.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import org.servalproject.json.JsonParser; 4 | import org.servalproject.servaldna.HttpJsonSerialiser; 5 | import org.servalproject.servaldna.ServalDInterfaceException; 6 | import org.servalproject.servaldna.Subscriber; 7 | import org.servalproject.servaldna.meshmb.MeshMBCommon; 8 | import org.servalproject.servaldna.meshmb.MessagePlyList; 9 | import org.servalproject.servaldna.meshmb.PlyMessage; 10 | import org.servalproject.servaldna.rhizome.RhizomeBundleList; 11 | import org.servalproject.servaldna.rhizome.RhizomeListBundle; 12 | 13 | import java.io.IOException; 14 | 15 | /** 16 | * Created by jeremy on 3/08/16. 17 | */ 18 | public class MessageFeed extends AbstractFutureList { 19 | private Subscriber id; 20 | private Peer peer; 21 | private String name; 22 | 23 | MessageFeed(Serval serval, Peer peer) { 24 | super(serval); 25 | this.peer = peer; 26 | Subscriber peerId = peer.getSubscriber(); 27 | if (peerId.signingKey!=null) 28 | this.id = peerId; 29 | } 30 | 31 | MessageFeed(Serval serval, Subscriber id) { 32 | super(serval); 33 | if (id == null || id.signingKey == null) 34 | throw new IllegalStateException(); 35 | this.id = id; 36 | } 37 | 38 | public Peer getPeer(){ 39 | return peer; 40 | } 41 | 42 | public Subscriber getId(){ 43 | if (id == null && peer != null){ 44 | // might have discovered the key some other way in the mean time 45 | Subscriber peerId = peer.getSubscriber(); 46 | if (peerId.signingKey != null) 47 | id = peerId; 48 | } 49 | return id; 50 | } 51 | 52 | @Override 53 | protected void start() { 54 | if (last == null && hasMore) 55 | return; 56 | super.start(); 57 | } 58 | 59 | private void findKey() throws IOException, ServalDInterfaceException, JsonParser.JsonParseException { 60 | if (id!=null) 61 | return; 62 | if (peer == null) 63 | throw new IllegalStateException(); 64 | 65 | // might have discovered the key some other way in the mean time 66 | Subscriber peerId = peer.getSubscriber(); 67 | if (peerId.signingKey != null){ 68 | id = peerId; 69 | return; 70 | } 71 | // look for a feed in rhizome 72 | RhizomeBundleList list = new RhizomeBundleList(serval.getResultClient()); 73 | try { 74 | list.setServiceFilter(MeshMBCommon.SERVICE); 75 | list.setSenderFilter(peer.getSubscriber().sid); 76 | list.connect(); 77 | RhizomeListBundle bundle = list.next(); 78 | if (bundle != null){ 79 | id = new Subscriber(peerId.sid, bundle.manifest.id, true); 80 | peer.updateSubscriber(id); 81 | } 82 | } finally { 83 | list.close(); 84 | } 85 | } 86 | 87 | @Override 88 | protected HttpJsonSerialiser openPast() throws ServalDInterfaceException, IOException, JsonParser.JsonParseException { 89 | findKey(); 90 | if (id == null) 91 | return null; 92 | MessagePlyList list = serval.getResultClient().meshmbListMessages(id.signingKey); 93 | this.name = list.getName(); 94 | if (peer != null) 95 | peer.updateFeedName(name); 96 | return list; 97 | } 98 | 99 | @Override 100 | protected HttpJsonSerialiser openFuture() throws ServalDInterfaceException, IOException, JsonParser.JsonParseException { 101 | findKey(); 102 | if (id == null) 103 | return null; 104 | MessagePlyList list = serval.getResultClient().meshmbListMessagesSince(id.signingKey, last==null?"":last.token); 105 | this.name = list.getName(); 106 | if (peer != null) 107 | peer.updateFeedName(name); 108 | return list; 109 | } 110 | 111 | public String getName(){ 112 | String peerName = name; 113 | if (peer != null) { 114 | if (peerName == null) 115 | peerName = peer.getFeedName(); 116 | if (peerName == null) 117 | peerName = peer.getName(); 118 | } 119 | return peerName; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/MessageList.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import org.servalproject.servaldna.HttpJsonSerialiser; 4 | import org.servalproject.servaldna.ServalDInterfaceException; 5 | import org.servalproject.servaldna.Subscriber; 6 | import org.servalproject.servaldna.meshms.MeshMSConversation; 7 | import org.servalproject.servaldna.meshms.MeshMSException; 8 | import org.servalproject.servaldna.meshms.MeshMSMessage; 9 | 10 | import java.io.IOException; 11 | 12 | /** 13 | * Created by jeremy on 11/07/16. 14 | */ 15 | public class MessageList extends AbstractFutureList { 16 | private final Messaging messaging; 17 | public final Subscriber self; 18 | public final Subscriber peer; 19 | 20 | MessageList(Serval serval, Messaging messaging, Subscriber self, Subscriber peer) { 21 | super(serval); 22 | this.messaging = messaging; 23 | this.self = self; 24 | this.peer = peer; 25 | } 26 | 27 | @Override 28 | protected void start() { 29 | if (last == null && hasMore) 30 | return; 31 | super.start(); 32 | } 33 | 34 | @Override 35 | protected HttpJsonSerialiser openPast() throws ServalDInterfaceException, IOException { 36 | return serval.getResultClient().meshmsListMessages(self.sid, peer.sid); 37 | } 38 | 39 | @Override 40 | protected HttpJsonSerialiser openFuture() throws ServalDInterfaceException, IOException { 41 | return serval.getResultClient().meshmsListMessagesSince(self.sid, peer.sid, last==null?"":last.token); 42 | } 43 | 44 | public void sendMessage(String message) throws ServalDInterfaceException, MeshMSException, IOException { 45 | if (serval.uiHandler.isOnThread()) 46 | throw new IllegalStateException(); 47 | serval.getResultClient().meshmsSendMessage(self.sid, peer.sid, message); 48 | } 49 | 50 | public boolean isRead(){ 51 | MeshMSConversation conv = messaging.getPrivateConversation(peer); 52 | return conv == null || conv.isRead; 53 | } 54 | 55 | public void markRead() throws ServalDInterfaceException, MeshMSException, IOException { 56 | if (serval.uiHandler.isOnThread()) 57 | throw new IllegalStateException(); 58 | serval.getResultClient().meshmsMarkAllMessagesRead(self.sid, peer.sid); 59 | messaging.refresh(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/Observer.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | public interface Observer { 4 | void updated(T obj); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/ObserverProxy.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import android.util.Log; 4 | 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | public class ObserverProxy implements IObserverSet, Observer { 9 | private final IObserverSet source; 10 | private final I item; 11 | private final Set> UIobservers = new HashSet<>(); 12 | private final Set> backgroundObservers = new HashSet<>(); 13 | private final CallbackHandler uiHandler; 14 | 15 | public ObserverProxy(CallbackHandler uiHandler, IObserverSet source, I item){ 16 | this.uiHandler = uiHandler; 17 | this.source = source; 18 | this.item = item; 19 | } 20 | 21 | @Override 22 | public void addUI(Observer observer) { 23 | if (UIobservers.isEmpty()) 24 | source.addUI(this); 25 | UIobservers.add(observer); 26 | } 27 | 28 | @Override 29 | public void removeUI(Observer observer) { 30 | UIobservers.remove(observer); 31 | if (UIobservers.isEmpty()) 32 | source.removeUI(this); 33 | } 34 | 35 | @Override 36 | public void addBackground(Observer observer) { 37 | if (backgroundObservers.isEmpty()) 38 | source.addBackground(this); 39 | backgroundObservers.add(observer); 40 | } 41 | 42 | @Override 43 | public void removeBackground(Observer observer) { 44 | backgroundObservers.remove(observer); 45 | if (backgroundObservers.isEmpty()) 46 | source.removeBackground(this); 47 | } 48 | 49 | @Override 50 | public I getObj() { 51 | return item; 52 | } 53 | 54 | @Override 55 | public void updated(S obj) { 56 | Set> observers = (uiHandler.isOnThread())?UIobservers:backgroundObservers; 57 | for(Observer o : observers) 58 | o.updated(item); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/ObserverSet.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import android.os.Handler; 4 | import android.os.Message; 5 | 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | /** 10 | * Created by jeremy on 4/05/16. 11 | */ 12 | public class ObserverSet implements Handler.Callback, IObserverSet { 13 | private final Set> UIobservers = new HashSet<>(); 14 | private final Set> backgroundObservers = new HashSet<>(); 15 | 16 | private final CallbackHandler UIhandler; 17 | private final CallbackHandler backgroundHandler; 18 | private final T obj; 19 | private static final String TAG = "ObserverSet"; 20 | 21 | private static final int UICallbacks = 1; 22 | private static final int BackgroundCallbacks = 2; 23 | 24 | public ObserverSet(Serval serval, T obj) { 25 | this.UIhandler = serval.uiHandler; 26 | this.backgroundHandler = serval.backgroundHandler; 27 | this.obj = obj; 28 | } 29 | 30 | public T getObj(){ 31 | return obj; 32 | } 33 | 34 | @Override 35 | public void addUI(Observer observer) { 36 | UIobservers.add(observer); 37 | } 38 | 39 | @Override 40 | public void removeUI(Observer observer) { 41 | UIobservers.remove(observer); 42 | } 43 | 44 | @Override 45 | public void addBackground(Observer observer) { 46 | backgroundObservers.add(observer); 47 | } 48 | 49 | @Override 50 | public void removeBackground(Observer observer) { 51 | backgroundObservers.remove(observer); 52 | } 53 | 54 | public void onUpdate() { 55 | if (!UIobservers.isEmpty()) 56 | UIhandler.sendEmptyMessage(this, UICallbacks); 57 | if (!backgroundObservers.isEmpty()) 58 | backgroundHandler.sendEmptyMessage(this, BackgroundCallbacks); 59 | } 60 | 61 | @Override 62 | public boolean handleMessage(Message message) { 63 | Set> observers; 64 | switch (message.what){ 65 | case UICallbacks: 66 | observers = UIobservers; 67 | break; 68 | case BackgroundCallbacks: 69 | observers = backgroundObservers; 70 | break; 71 | default: 72 | return false; 73 | } 74 | for (Observer observer : observers) 75 | observer.updated(obj); 76 | return true; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/Peer.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid; 2 | 3 | import android.util.Log; 4 | 5 | import org.servalproject.servaldna.RouteLink; 6 | import org.servalproject.servaldna.ServalDCommand; 7 | import org.servalproject.servaldna.Subscriber; 8 | import org.servalproject.servaldna.SubscriberId; 9 | 10 | /** 11 | * Created by jeremy on 4/05/16. 12 | */ 13 | public final class Peer implements Comparable { 14 | private static long nextId = 0; 15 | private final long id; 16 | 17 | Peer(Serval serval, Subscriber subscriber) { 18 | this.subscriber = subscriber; 19 | observers = new ObserverSet<>(serval, this); 20 | id = nextId++; 21 | } 22 | 23 | public final ObserverSet observers; 24 | private Subscriber subscriber; 25 | 26 | public Subscriber getSubscriber() { 27 | return subscriber; 28 | } 29 | 30 | public void updateSubscriber(Subscriber subscriber) { 31 | if (this.subscriber.sid.equals(subscriber.sid) 32 | && this.subscriber.signingKey == null) { 33 | this.subscriber = subscriber; 34 | observers.onUpdate(); 35 | } 36 | } 37 | 38 | ServalDCommand.LookupResult lookup; 39 | 40 | public String getDid() { 41 | return lookup == null ? null : lookup.did; 42 | } 43 | 44 | public String getName() { 45 | return lookup == null ? null : lookup.name; 46 | } 47 | 48 | void update(ServalDCommand.LookupResult result) { 49 | lookup = result; 50 | observers.onUpdate(); 51 | } 52 | 53 | RouteLink link; 54 | Interface netInterface; 55 | Peer priorHop; 56 | 57 | public boolean isReachable() { 58 | return link != null; 59 | } 60 | 61 | public int getHopCount(){ 62 | return link != null ? link.hop_count : -1; 63 | } 64 | 65 | public Peer getPriorHop(){ 66 | return isReachable() ? priorHop : null; 67 | } 68 | 69 | public Interface getNetInterface(){ 70 | return netInterface; 71 | } 72 | 73 | public boolean isContact() { 74 | return false; 75 | } 76 | 77 | public boolean isBlocked() { 78 | return false; 79 | } 80 | 81 | void update(RouteLink route, Interface netInterface, Peer priorHop) { 82 | link = route.isReachable() ? route : null; 83 | this.netInterface = netInterface; 84 | this.priorHop = priorHop; 85 | observers.onUpdate(); 86 | } 87 | 88 | public long getId() { 89 | // return a stable id, for UI list binding. 90 | return id; 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | return "Peer{" + 96 | "subscriber=" + subscriber + 97 | ", lookup=" + lookup + 98 | ", link=" + link + 99 | ", interface=" + (netInterface==null?"None":netInterface.name) + 100 | ", prior=" + (priorHop==null?"None":priorHop.subscriber.sid.abbreviation()) + 101 | '}'; 102 | } 103 | 104 | private String feedName; 105 | public String getFeedName(){ 106 | return feedName; 107 | } 108 | 109 | public String displayName() { 110 | String n = feedName; 111 | if (n == null || "".equals(n)) 112 | n = getName(); 113 | if (n == null || "".equals(n)) 114 | n = getDid(); 115 | if (n == null || "".equals(n)) 116 | n = subscriber.sid.abbreviation(); 117 | return n; 118 | } 119 | 120 | public void updateFeedName(String name) { 121 | if (feedName == null && name == null) 122 | return; 123 | if (name != null && name.equals(feedName)) 124 | return; 125 | feedName = name; 126 | observers.onUpdate(); 127 | } 128 | 129 | public MessageFeed getFeed() { 130 | return new MessageFeed(Serval.getInstance(), this); 131 | } 132 | 133 | @Override 134 | public int compareTo(Peer another) { 135 | int r = this.displayName().compareTo(another.displayName()); 136 | if (r!=0) 137 | return r; 138 | return subscriber.sid.toHex().compareTo(another.subscriber.sid.toHex()); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/networking/AbstractListObserver.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid.networking; 2 | 3 | import org.servalproject.mid.ListObserver; 4 | 5 | public abstract class AbstractListObserver implements ListObserver { 6 | @Override 7 | public void added(T obj) { 8 | 9 | } 10 | 11 | @Override 12 | public void removed(T obj) { 13 | 14 | } 15 | 16 | @Override 17 | public void updated(T obj) { 18 | 19 | } 20 | 21 | @Override 22 | public void reset() { 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/networking/FlightModeObserver.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid.networking; 2 | 3 | import android.content.ContentResolver; 4 | import android.database.ContentObserver; 5 | import android.net.Uri; 6 | import android.os.Build; 7 | import android.os.Handler; 8 | import android.provider.Settings; 9 | import android.util.Log; 10 | 11 | /** 12 | * Created by jeremy on 31/05/17. 13 | */ 14 | 15 | public class FlightModeObserver extends ContentObserver { 16 | private final Networks networks; 17 | private final ContentResolver resolver; 18 | boolean flightMode; 19 | String flightModeRadios = ""; 20 | String flightModeToggleable= ""; 21 | 22 | private static final String TAG = "FlightMode"; 23 | 24 | public FlightModeObserver(Networks networks, ContentResolver resolver, Handler handler) { 25 | super(handler); 26 | this.networks = networks; 27 | this.resolver = resolver; 28 | } 29 | 30 | void register(){ 31 | if (Build.VERSION.SDK_INT >= 17) { 32 | resolver.registerContentObserver(Settings.Global.CONTENT_URI, true, this); 33 | }else { 34 | resolver.registerContentObserver(Settings.System.CONTENT_URI, true, this); 35 | } 36 | onChange(false, null); 37 | } 38 | 39 | void unregister(){ 40 | resolver.unregisterContentObserver(this); 41 | } 42 | 43 | @Override 44 | public void onChange(boolean selfChange) { 45 | onChange(selfChange, null); 46 | } 47 | 48 | @SuppressWarnings("deprecation") 49 | @Override 50 | public void onChange(boolean selfChange, Uri uri) { 51 | boolean airplaneMode; 52 | String airplaneRadios; 53 | String airplaneToggleable; 54 | 55 | try { 56 | if (Build.VERSION.SDK_INT >= 17){ 57 | airplaneMode = Settings.Global.getInt(resolver, Settings.Global.AIRPLANE_MODE_ON) !=0; 58 | airplaneRadios = Settings.Global.getString(resolver, Settings.Global.AIRPLANE_MODE_RADIOS); 59 | airplaneToggleable= Settings.Global.getString(resolver, "airplane_mode_toggleable_radios"); 60 | }else { 61 | airplaneMode = Settings.System.getInt(resolver, Settings.System.AIRPLANE_MODE_ON) !=0; 62 | airplaneRadios = Settings.System.getString(resolver, Settings.System.AIRPLANE_MODE_RADIOS); 63 | airplaneToggleable = Settings.System.getString(resolver, "airplane_mode_toggleable_radios"); 64 | } 65 | } catch (Settings.SettingNotFoundException e) { 66 | throw new IllegalStateException(e); 67 | } 68 | 69 | Log.v(TAG, airplaneMode+", ["+airplaneRadios+"], ["+airplaneToggleable+"]"); 70 | 71 | if (airplaneMode == this.flightMode 72 | && this.flightModeRadios.equals(airplaneRadios) 73 | && this.flightModeToggleable.equals(airplaneToggleable)) 74 | return; 75 | 76 | this.flightMode = airplaneMode; 77 | this.flightModeRadios = airplaneRadios; 78 | this.flightModeToggleable = airplaneToggleable; 79 | networks.onFlightModeChanged(); 80 | } 81 | } -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/networking/NetworkInfo.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid.networking; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.util.Log; 6 | 7 | import org.servalproject.mid.ObserverSet; 8 | import org.servalproject.mid.Serval; 9 | import org.servalproject.servalchat.R; 10 | 11 | /** 12 | * Created by jeremy on 2/11/16. 13 | */ 14 | public abstract class NetworkInfo { 15 | private static final String TAG = "NetworkInfo"; 16 | public enum State{ 17 | Off(R.string.stopped), 18 | Starting(R.string.starting), 19 | Stopping(R.string.stopping), 20 | On(R.string.started), 21 | Error(R.string.unknown_error); 22 | 23 | public final int stringResource; 24 | State(int stringResource){ 25 | this.stringResource = stringResource; 26 | } 27 | 28 | public String getString(Context context){ 29 | if (stringResource == 0) 30 | throw new IllegalArgumentException(); 31 | return context.getString(stringResource); 32 | } 33 | } 34 | 35 | public final ObserverSet observers; 36 | protected final Serval serval; 37 | 38 | public NetworkInfo(Serval serval){ 39 | this.serval = serval; 40 | observers = new ObserverSet<>(serval, this); 41 | } 42 | 43 | public abstract String getName(Context context); 44 | public abstract void enable(Context context); 45 | public abstract void disable(Context context); 46 | public abstract Intent getIntent(Context context); 47 | public abstract String getRadioName(); 48 | 49 | public boolean isUsable(){ 50 | return isOn(); 51 | } 52 | 53 | public boolean isOn(){ 54 | switch (getState()){ 55 | case On: 56 | return true; 57 | default: 58 | return false; 59 | } 60 | } 61 | 62 | public void toggle(Context context){ 63 | if (isUsable()) 64 | disable(context); 65 | else 66 | enable(context); 67 | } 68 | 69 | public String getStatus(Context context){ 70 | return getState().getString(context); 71 | } 72 | 73 | private State state = State.Error; 74 | protected void setState(State state){ 75 | if (state == this.state) 76 | return; 77 | Log.v(TAG, getName(serval.context)+" changed to "+state); 78 | this.state = state; 79 | observers.onUpdate(); 80 | } 81 | 82 | public State getState(){ 83 | return state; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/networking/WifiClient.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid.networking; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.net.wifi.WifiManager; 6 | import android.util.Log; 7 | 8 | import org.servalproject.mid.Serval; 9 | import org.servalproject.servalchat.R; 10 | 11 | /** 12 | * Created by jeremy on 2/11/16. 13 | */ 14 | public class WifiClient extends NetworkInfo{ 15 | private WifiManager manager; 16 | private static final String TAG = "WifiClient"; 17 | 18 | protected WifiClient(Serval serval) { 19 | super(serval); 20 | manager = (WifiManager) serval.context.getSystemService(Context.WIFI_SERVICE); 21 | setState(statusToState(manager.getWifiState())); 22 | } 23 | 24 | @Override 25 | public String getName(Context context) { 26 | return context.getString(R.string.wifi_client); 27 | } 28 | 29 | @Override 30 | public String getStatus(Context context) { 31 | if (getState()==State.Off && Networks.getInstance().getGoal() == Networks.WifiGoal.ClientOn) 32 | return context.getString(R.string.queued); 33 | return super.getStatus(context); 34 | } 35 | 36 | void setEnabled(boolean enabled){ 37 | manager.setWifiEnabled(enabled); 38 | } 39 | 40 | @Override 41 | public void enable(Context context) { 42 | Networks.getInstance().setWifiGoal(Networks.WifiGoal.ClientOn); 43 | } 44 | 45 | @Override 46 | public void disable(Context context) { 47 | Networks.getInstance().setWifiGoal(Networks.WifiGoal.Off); 48 | } 49 | 50 | @Override 51 | public Intent getIntent(Context context) { 52 | return new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK); 53 | } 54 | 55 | @Override 56 | public String getRadioName() { 57 | return "wifi"; 58 | } 59 | 60 | public static State statusToState(int state) { 61 | switch (state) { 62 | case WifiManager.WIFI_STATE_DISABLED: 63 | return State.Off; 64 | case WifiManager.WIFI_STATE_DISABLING: 65 | return State.Stopping; 66 | case WifiManager.WIFI_STATE_ENABLED: 67 | return State.On; 68 | case WifiManager.WIFI_STATE_ENABLING: 69 | return State.Starting; 70 | default: 71 | Log.v(TAG, "Unknown state: "+state); 72 | return State.Error; 73 | } 74 | } 75 | 76 | public void onStateChanged(Intent intent) { 77 | setState(statusToState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, -1))); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/networking/WifiHotspotChanges.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid.networking; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import org.servalproject.mid.Serval; 8 | 9 | /** 10 | * Created by jeremy on 2/11/16. 11 | */ 12 | public class WifiHotspotChanges extends BroadcastReceiver { 13 | public static final String WIFI_AP_STATE_CHANGED_ACTION = "android.net.wifi.WIFI_AP_STATE_CHANGED"; 14 | 15 | @Override 16 | public void onReceive(Context context, Intent intent) { 17 | String action = intent.getAction(); 18 | Hotspot hotspot = Networks.getInstance().wifiHotspot; 19 | if (hotspot == null) 20 | return; 21 | 22 | if (action.equals(WIFI_AP_STATE_CHANGED_ACTION)) { 23 | hotspot.onStateChanged(intent); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/networking/WifiNetworkChanges.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid.networking; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.net.wifi.WifiManager; 7 | 8 | import org.servalproject.mid.Serval; 9 | 10 | public class WifiNetworkChanges extends BroadcastReceiver { 11 | @Override 12 | public void onReceive(Context context, Intent intent) { 13 | String action = intent.getAction(); 14 | WifiClient wifiClient = Networks.getInstance().wifiClient; 15 | 16 | if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { 17 | wifiClient.onStateChanged(intent); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/networking/bluetooth/BlueToothInfo.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid.networking.bluetooth; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.provider.Settings; 7 | 8 | import org.servalproject.mid.Serval; 9 | import org.servalproject.mid.networking.NetworkInfo; 10 | import org.servalproject.servalchat.R; 11 | 12 | /** 13 | * Created by jeremy on 8/11/16. 14 | */ 15 | 16 | public class BlueToothInfo extends NetworkInfo { 17 | private final BlueToothControl control; 18 | 19 | BlueToothInfo(BlueToothControl control, Serval serval) { 20 | super(serval); 21 | this.control = control; 22 | } 23 | @Override 24 | public String getName(Context context) { 25 | return context.getString(R.string.bluetooth); 26 | } 27 | 28 | @Override 29 | public String getStatus(Context context) { 30 | if (getState()==State.On && !control.isDiscoverable()) 31 | return context.getString(R.string.not_discoverable); 32 | return super.getStatus(context); 33 | } 34 | 35 | @Override 36 | public void enable(Context context) { 37 | control.requestDiscoverable(context); 38 | } 39 | 40 | @Override 41 | public void disable(Context context) { 42 | if (control.isEnabled()) 43 | control.adapter.disable(); 44 | } 45 | 46 | @Override 47 | public Intent getIntent(Context context) { 48 | return new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); 49 | } 50 | 51 | @Override 52 | public String getRadioName() { 53 | return "bluetooth"; 54 | } 55 | 56 | @Override 57 | public boolean isUsable() { 58 | return control.isDiscoverable(); 59 | } 60 | 61 | static State statusToState(int status){ 62 | switch (status){ 63 | case BluetoothAdapter.STATE_ON: 64 | return State.On; 65 | case BluetoothAdapter.STATE_OFF: 66 | return State.Off; 67 | case BluetoothAdapter.STATE_TURNING_ON: 68 | return State.Starting; 69 | case BluetoothAdapter.STATE_TURNING_OFF: 70 | return State.Stopping; 71 | default: 72 | return State.Error; 73 | } 74 | } 75 | 76 | void setState(int state) { 77 | setState(statusToState(state)); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/networking/bluetooth/BluetoothNetworkChanges.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid.networking.bluetooth; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.bluetooth.BluetoothDevice; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | 9 | import org.servalproject.mid.Serval; 10 | import org.servalproject.mid.networking.Networks; 11 | 12 | public class BluetoothNetworkChanges extends BroadcastReceiver { 13 | @Override 14 | public void onReceive(Context context, Intent intent) { 15 | BlueToothControl blueTooth = Networks.getInstance().blueTooth; 16 | if (blueTooth == null) 17 | return; 18 | String action = intent.getAction(); 19 | if (action.equals(BluetoothDevice.ACTION_FOUND)) { 20 | blueTooth.onFound(intent); 21 | } else if (action.equals(BluetoothDevice.ACTION_NAME_CHANGED)) { 22 | blueTooth.onRemoteNameChanged(intent); 23 | } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) { 24 | blueTooth.scanner.onDiscoveryStarted(); 25 | } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { 26 | blueTooth.scanner.onDiscoveryFinished(); 27 | } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 28 | blueTooth.onStateChange(intent); 29 | } else if (action.equals(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED)) { 30 | blueTooth.scanner.onScanModeChanged(intent); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/networking/bluetooth/Connector.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid.networking.bluetooth; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.bluetooth.BluetoothSocket; 5 | import android.os.Build; 6 | import android.os.SystemClock; 7 | import android.util.Log; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * Created by jeremy on 7/04/15. 13 | */ 14 | class Connector implements Runnable{ 15 | private final BlueToothControl control; 16 | private final BluetoothAdapter adapter; 17 | public final PeerState peer; 18 | private final boolean paired; 19 | private long connectionStarted=0; 20 | private boolean connecting = false; 21 | private BluetoothSocket socket = null; 22 | 23 | private static final String TAG = "Connector"; 24 | 25 | Connector(BlueToothControl control, PeerState peer, boolean paired) { 26 | this.control = control; 27 | this.adapter = control.adapter; 28 | this.peer = peer; 29 | this.paired = paired; 30 | } 31 | 32 | @Override 33 | public void run() { 34 | connectionStarted = SystemClock.elapsedRealtime(); 35 | 36 | try { 37 | if (paired) 38 | socket = peer.device.createRfcommSocketToServiceRecord(BlueToothControl.SECURE_UUID); 39 | else if (Build.VERSION.SDK_INT >= 10) { 40 | socket = peer.device.createInsecureRfcommSocketToServiceRecord(BlueToothControl.INSECURE_UUID); 41 | } 42 | } catch (IOException e){ 43 | Log.v(TAG, "Failed to create socket", e); 44 | } 45 | 46 | if (socket!=null) { 47 | try { 48 | socket.connect(); 49 | Log.v(TAG, "Connected to " + peer); 50 | int bias = peer.device.getName().toLowerCase().compareTo(control.adapter.getName().toLowerCase()) * 500; 51 | peer.onConnected(socket, paired, bias); 52 | } catch (IOException e) { 53 | try { 54 | socket.close(); 55 | } catch (IOException e1) { 56 | } 57 | Log.v(TAG, "Connection failed to " + peer); 58 | peer.onConnectionFailed(); 59 | } 60 | socket = null; 61 | } 62 | connecting = false; 63 | control.remove(this); 64 | } 65 | 66 | public void connect() { 67 | connecting = true; 68 | control.serval.runOnThreadPool(this); 69 | } 70 | 71 | public void cancel() { 72 | BluetoothSocket s = socket; 73 | if (s == null) 74 | return; 75 | try { 76 | Log.v(TAG, "Cancelling connection to " + peer); 77 | s.close(); 78 | } catch (IOException e) { 79 | } 80 | } 81 | 82 | public synchronized void moveNext() { 83 | if (!connecting) 84 | connect(); 85 | else if (connectionStarted!=0 && SystemClock.elapsedRealtime() - connectionStarted > 5000) 86 | cancel(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/networking/bluetooth/PeerReader.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid.networking.bluetooth; 2 | 3 | import android.bluetooth.BluetoothSocket; 4 | import android.os.SystemClock; 5 | import android.util.Log; 6 | 7 | import java.io.EOFException; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | 11 | /** 12 | * Created by jeremy on 7/04/15. 13 | */ 14 | class PeerReader implements Runnable, Comparable { 15 | private final BlueToothControl control; 16 | public final boolean secure; 17 | public final BluetoothSocket socket; 18 | private final PeerState peer; 19 | private final int bias; 20 | private Thread thread; 21 | public final String name; 22 | long lastReceived; 23 | long lastWritten; 24 | 25 | private static int __id = 0; 26 | 27 | PeerReader(BlueToothControl control, PeerState peer, BluetoothSocket socket, boolean secure, int bias) { 28 | name = "PeerReader" + (__id++); 29 | this.control = control; 30 | this.peer = peer; 31 | this.socket = socket; 32 | this.secure = secure; 33 | this.bias = bias; 34 | lastReceived = SystemClock.elapsedRealtime(); 35 | } 36 | 37 | public void start() { 38 | if (thread != null) 39 | return; 40 | thread = new Thread(this, "Reader" + peer.device.getAddress()); 41 | thread.start(); 42 | } 43 | 44 | public boolean isRunning() { 45 | return thread != null; 46 | } 47 | 48 | @Override 49 | public void run() { 50 | try { 51 | InputStream in = socket.getInputStream(); 52 | byte buff[] = new byte[BlueToothControl.MTU + 2]; 53 | int offset = 0; 54 | while (thread == Thread.currentThread()) { 55 | if (offset >= 2) { 56 | int msgLen = (buff[0] & 0xFF) | ((buff[1] & 0xFF) << 8); 57 | if (msgLen > buff.length - 2 || msgLen < 0) 58 | throw new IllegalStateException(msgLen + " is greater than the link MTU"); 59 | if (offset >= msgLen + 2) { 60 | control.receivedPacket(peer.addrBytes, buff, 2, msgLen); 61 | if (offset > msgLen + 2) 62 | System.arraycopy(buff, msgLen + 2, buff, 0, offset - (msgLen + 2)); 63 | offset -= msgLen + 2; 64 | continue; 65 | } 66 | } 67 | int len = in.read(buff, offset, buff.length - offset); 68 | if (len < 0) 69 | throw new EOFException(); 70 | lastReceived = SystemClock.elapsedRealtime(); 71 | offset += len; 72 | } 73 | } catch (IOException e) { 74 | Log.e(name, e.getMessage(), e); 75 | }finally { 76 | if (thread == Thread.currentThread()) 77 | thread = null; 78 | 79 | peer.onClosed(this); 80 | } 81 | } 82 | 83 | @Override 84 | public int compareTo(PeerReader peerReader) { 85 | if (peerReader == this) 86 | return 0; 87 | if (this.bias + this.lastReceived < peerReader.bias + peerReader.lastReceived) 88 | return -1; 89 | return 1; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/networking/bluetooth/Scanner.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid.networking.bluetooth; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.content.Intent; 5 | import android.os.SystemClock; 6 | import android.util.Log; 7 | 8 | import org.servalproject.mid.networking.NetworkInfo; 9 | 10 | public class Scanner{ 11 | private final BlueToothControl control; 12 | private final BluetoothAdapter adapter; 13 | private int scanMode; 14 | private long lastScanStart=0; 15 | private long lastPeerScanned=0; 16 | private long lastScanEnd=1; 17 | 18 | private static final String TAG = "BTScanner"; 19 | 20 | Scanner(BlueToothControl control, BluetoothAdapter adapter){ 21 | this.control = control; 22 | this.adapter = adapter; 23 | setState(); 24 | } 25 | 26 | private void setState(int newMode){ 27 | if (scanMode == newMode) 28 | return; 29 | scanMode = newMode; 30 | Log.v(TAG, "Scan mode changed; " + scanMode + " " + adapter.isEnabled()); 31 | lastScanStart=0; 32 | lastPeerScanned=0; 33 | lastScanEnd=1; 34 | if (adapter.isEnabled() && adapter.isDiscovering()) 35 | lastScanStart = SystemClock.elapsedRealtime(); 36 | } 37 | 38 | void setState(){ 39 | setState(adapter.getScanMode()); 40 | } 41 | 42 | public void onScanModeChanged(Intent intent) { 43 | setState(intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, 0)); 44 | } 45 | 46 | public void onDiscoveryStarted() { 47 | lastScanStart = SystemClock.elapsedRealtime(); 48 | Log.v(TAG, "Discovery Started"); 49 | // TODO set alarm to cancel / restart bluetooth 50 | } 51 | 52 | public void onDiscoveryFinished() { 53 | lastScanEnd = SystemClock.elapsedRealtime(); 54 | Log.v(TAG, "Discovery finished"); 55 | control.runNext(); 56 | } 57 | 58 | public void onPeerScanned(){ 59 | lastPeerScanned = SystemClock.elapsedRealtime(); 60 | } 61 | 62 | public boolean isDiscovering() { 63 | return adapter.isDiscovering(); 64 | } 65 | 66 | public void startDiscovery() { 67 | if (control.networkInfo.getState() != NetworkInfo.State.On || !adapter.isEnabled()) 68 | return; 69 | if (adapter.startDiscovery()) 70 | lastScanStart = SystemClock.elapsedRealtime(); 71 | } 72 | 73 | private static final int RESCAN_INTERVAL = 30000; 74 | private static final int SCAN_IDLE_TIME = 5000; 75 | private static final int CANCEL_BROKEN = 10000; 76 | private static final int POLL_INTERVAL = 1000; 77 | 78 | public int nextScanAction(Connector item){ 79 | long now = SystemClock.elapsedRealtime(); 80 | 81 | if (lastScanStart < lastScanEnd){ 82 | if (item!=null) { 83 | item.moveNext(); 84 | return POLL_INTERVAL; 85 | } 86 | 87 | long nextScanDelay = lastScanEnd + RESCAN_INTERVAL - now; 88 | if (nextScanDelay <=0) { 89 | startDiscovery(); 90 | return POLL_INTERVAL; 91 | } 92 | return (int) nextScanDelay; 93 | } 94 | 95 | int delay = (item == null) ? RESCAN_INTERVAL : SCAN_IDLE_TIME; 96 | 97 | long cancelAfter = Math.max(lastScanStart, lastPeerScanned) + delay - now; 98 | if (cancelAfter <=0){ 99 | cancelDiscovery(); 100 | if (cancelAfter <= - CANCEL_BROKEN && cancelAfter >= - RESCAN_INTERVAL){ 101 | if (item == null) 102 | startDiscovery(); 103 | else { 104 | item.moveNext(); 105 | } 106 | } 107 | return POLL_INTERVAL; 108 | } 109 | return (int) cancelAfter; 110 | } 111 | 112 | public void cancelDiscovery(){ 113 | if (control.networkInfo.getState() != NetworkInfo.State.On || !adapter.isEnabled()) 114 | return; 115 | if (adapter.isDiscovering()) 116 | adapter.cancelDiscovery(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/mid/networking/bluetooth/SocketListener.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.mid.networking.bluetooth; 2 | 3 | import android.bluetooth.BluetoothServerSocket; 4 | import android.bluetooth.BluetoothSocket; 5 | import android.util.Log; 6 | 7 | import java.io.IOException; 8 | import java.util.UUID; 9 | 10 | class SocketListener extends Thread { 11 | private BlueToothControl blueToothControl; 12 | private final BluetoothServerSocket socket; 13 | private final boolean secure; 14 | private boolean running = true; 15 | 16 | private static final String appName = "Serval"; 17 | private static final String TAG = "BTListener"; 18 | 19 | private SocketListener(BlueToothControl blueToothControl, BluetoothServerSocket socket, boolean secure, UUID uuid) { 20 | super(secure ? "BluetoothSL" : "BluetoothISL"); 21 | this.blueToothControl = blueToothControl; 22 | this.secure = secure; 23 | this.socket = socket; 24 | this.start(); 25 | Log.v(TAG, "Listening for; " + uuid); 26 | } 27 | 28 | public static SocketListener create(BlueToothControl blueToothControl, boolean secure, UUID uuid) throws IOException { 29 | if (!blueToothControl.adapter.isEnabled()) 30 | return null; 31 | BluetoothServerSocket socket; 32 | if (secure){ 33 | socket = blueToothControl.adapter.listenUsingRfcommWithServiceRecord(appName, uuid); 34 | }else{ 35 | socket = blueToothControl.adapter.listenUsingInsecureRfcommWithServiceRecord(appName, uuid); 36 | } 37 | return new SocketListener(blueToothControl, socket, secure, uuid); 38 | } 39 | 40 | public void close() { 41 | try { 42 | running = false; 43 | socket.close(); 44 | } catch (IOException e) { 45 | Log.e(TAG, e.getMessage(), e); 46 | } 47 | } 48 | 49 | @Override 50 | public void run() { 51 | while (running && blueToothControl.adapter.isEnabled()) { 52 | try { 53 | BluetoothSocket client = socket.accept(); 54 | Log.v(TAG, "Incoming connection from " + client.getRemoteDevice().getAddress()); 55 | PeerState peer = blueToothControl.getPeer(client.getRemoteDevice()); 56 | int bias = peer.device.getName().toLowerCase().compareTo(blueToothControl.adapter.getName().toLowerCase()) * -500; 57 | peer.onConnected(client, secure, bias); 58 | } catch (Exception e) { 59 | Log.e(TAG, e.getMessage(), e); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/ForegroundService.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat; 2 | 3 | import android.app.PendingIntent; 4 | import android.app.Service; 5 | import android.content.Intent; 6 | import android.os.IBinder; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.app.NotificationCompat; 9 | 10 | import org.servalproject.servalchat.navigation.MainActivity; 11 | import org.servalproject.servalchat.navigation.Navigation; 12 | 13 | /** 14 | * Created by jeremy on 19/10/16. 15 | * Create a notification so that android wont just kill our process while a network is viable 16 | */ 17 | public class ForegroundService extends Service { 18 | 19 | @Nullable 20 | @Override 21 | public IBinder onBind(Intent intent) { 22 | return null; 23 | } 24 | 25 | @Override 26 | public int onStartCommand(Intent intent, int flags, int startId) { 27 | if (intent != null && intent.getBooleanExtra("foreground", false)) { 28 | Intent navIntent = MainActivity.getIntentFor(this, Navigation.Networking, null, null, null); 29 | PendingIntent pending = PendingIntent.getActivity(this, 0, navIntent, PendingIntent.FLAG_UPDATE_CURRENT); 30 | 31 | NotificationCompat.Builder builder = 32 | new NotificationCompat.Builder(this, App.CHANNEL_ID) 33 | .setSmallIcon(R.mipmap.serval_head) 34 | .setContentTitle(getString(R.string.foreground_title)) 35 | .setContentText(getString(R.string.foreground_text)) 36 | .setContentIntent(pending); 37 | this.startForeground(-1, builder.build()); 38 | return START_STICKY; 39 | } else { 40 | stopForeground(true); 41 | stopSelf(); 42 | return START_NOT_STICKY; 43 | } 44 | } 45 | 46 | @Override 47 | public void onDestroy() { 48 | super.onDestroy(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/SampleData.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat; 2 | 3 | import org.servalproject.mid.Identity; 4 | import org.servalproject.mid.IdentityFeed; 5 | import org.servalproject.mid.ListObserver; 6 | import org.servalproject.mid.MessageList; 7 | import org.servalproject.mid.Observer; 8 | import org.servalproject.mid.Peer; 9 | import org.servalproject.mid.Serval; 10 | import org.servalproject.mid.Server; 11 | import org.servalproject.mid.networking.AbstractListObserver; 12 | import org.servalproject.servaldna.ServalDInterfaceException; 13 | import org.servalproject.servaldna.meshmb.MeshMBCommon; 14 | import org.servalproject.servaldna.meshms.MeshMSException; 15 | 16 | import java.io.IOException; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | /** 21 | * Created by jeremy on 1/05/17. 22 | */ 23 | 24 | public class SampleData { 25 | private static SampleData instance; 26 | 27 | private final Serval serval; 28 | public SampleData(final Serval serval) { 29 | this.serval = serval; 30 | serval.identities.listObservers.addBackground(new AbstractListObserver() { 31 | @Override 32 | public void reset() { 33 | createTestData(serval); 34 | } 35 | }); 36 | } 37 | 38 | public static void createTestData(Serval serval){ 39 | try { 40 | if (!serval.identities.isLoaded() 41 | || serval.identities.getIdentities().size()>0) 42 | return; 43 | 44 | List identities = new ArrayList<>(); 45 | List peers = new ArrayList<>(); 46 | for (int i=0;i<4;i++) { 47 | Identity id = serval.identities.addIdentity("", "Sample User " + i, ""); 48 | peers.add(serval.knownPeers.getPeer(id.subscriber)); 49 | identities.add(id); 50 | } 51 | for (int i=0;i 28 | implements ILifecycle, INavigate, ListObserver { 29 | private MainActivity activity; 30 | private final SortedList list; 31 | private Identity identity; 32 | private int generation = -1; 33 | 34 | public BlockList(Context context, @Nullable AttributeSet attrs) { 35 | super(context, attrs, R.string.empty_blocklist); 36 | listAdapter.setHasStableIds(true); 37 | setHasFixedSize(true); 38 | RecyclerHelper.createLayoutManager(this, true, false); 39 | RecyclerHelper.createDivider(this); 40 | SortedListAdapterCallback listCallback = new SortedListAdapterCallback(listAdapter) { 41 | @Override 42 | public int compare(Peer o1, Peer o2) { 43 | return o1.compareTo(o2); 44 | } 45 | 46 | @Override 47 | public boolean areContentsTheSame(Peer oldItem, Peer newItem) { 48 | return oldItem.equals(newItem); 49 | } 50 | 51 | @Override 52 | public boolean areItemsTheSame(Peer item1, Peer item2) { 53 | return item1.equals(item2); 54 | } 55 | }; 56 | list = new SortedList(Peer.class, listCallback); 57 | } 58 | 59 | @Override 60 | protected PeerHolder createHolder(ViewGroup parent, int viewType) { 61 | return new PeerHolder(activity, parent); 62 | } 63 | 64 | @Override 65 | protected void bind(PeerHolder holder, Peer item) { 66 | holder.bind(item); 67 | } 68 | 69 | @Override 70 | protected Peer get(int position) { 71 | return list.get(position); 72 | } 73 | 74 | @Override 75 | protected int getCount() { 76 | return list.size(); 77 | } 78 | 79 | @Override 80 | public void added(Peer obj) { 81 | list.add(obj); 82 | } 83 | 84 | @Override 85 | public void removed(Peer obj) { 86 | list.remove(obj); 87 | } 88 | 89 | @Override 90 | public void updated(Peer obj) { 91 | } 92 | 93 | @Override 94 | public void reset() { 95 | list.beginBatchedUpdates(); 96 | list.clear(); 97 | list.addAll(identity.messaging.getBlockList()); 98 | list.endBatchedUpdates(); 99 | } 100 | 101 | @Override 102 | public void onVisible() { 103 | int g = identity.messaging.observeBlockList.add(this); 104 | if (g != generation) 105 | reset(); 106 | } 107 | 108 | @Override 109 | public void onHidden() { 110 | generation = identity.messaging.observeBlockList.remove(this); 111 | } 112 | 113 | @Override 114 | public void onDetach(boolean configChanging) { 115 | 116 | } 117 | 118 | @Override 119 | public ILifecycle onAttach(MainActivity activity, Navigation n, Identity id, Peer peer, Bundle args) { 120 | this.activity = activity; 121 | this.identity = id; 122 | return this; 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/feeds/Contacts.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.feeds; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.util.SortedList; 7 | import android.support.v7.widget.util.SortedListAdapterCallback; 8 | import android.util.AttributeSet; 9 | import android.util.Log; 10 | import android.view.ViewGroup; 11 | 12 | import org.servalproject.mid.Identity; 13 | import org.servalproject.mid.ListObserver; 14 | import org.servalproject.mid.Peer; 15 | import org.servalproject.servalchat.R; 16 | import org.servalproject.servalchat.navigation.ILifecycle; 17 | import org.servalproject.servalchat.navigation.INavigate; 18 | import org.servalproject.servalchat.navigation.MainActivity; 19 | import org.servalproject.servalchat.navigation.Navigation; 20 | import org.servalproject.servalchat.peer.PeerHolder; 21 | import org.servalproject.servalchat.views.RecyclerHelper; 22 | import org.servalproject.servalchat.views.SimpleRecyclerView; 23 | 24 | /** 25 | * Created by jeremy on 30/05/17. 26 | */ 27 | 28 | public class Contacts extends SimpleRecyclerView 29 | implements ILifecycle, INavigate, ListObserver { 30 | private MainActivity activity; 31 | private final SortedList list; 32 | private Identity identity; 33 | private int generation = -1; 34 | private static final String TAG = "Contacts"; 35 | 36 | public Contacts(Context context, @Nullable AttributeSet attrs) { 37 | super(context, attrs, R.string.empty_contacts); 38 | listAdapter.setHasStableIds(true); 39 | setHasFixedSize(true); 40 | RecyclerHelper.createLayoutManager(this, true, false); 41 | RecyclerHelper.createDivider(this); 42 | SortedListAdapterCallback listCallback = new SortedListAdapterCallback(listAdapter) { 43 | @Override 44 | public int compare(Peer o1, Peer o2) { 45 | return o1.compareTo(o2); 46 | } 47 | 48 | @Override 49 | public boolean areContentsTheSame(Peer oldItem, Peer newItem) { 50 | return oldItem.equals(newItem); 51 | } 52 | 53 | @Override 54 | public boolean areItemsTheSame(Peer item1, Peer item2) { 55 | return item1.equals(item2); 56 | } 57 | }; 58 | list = new SortedList(Peer.class, listCallback); 59 | } 60 | 61 | @Override 62 | protected PeerHolder createHolder(ViewGroup parent, int viewType) { 63 | return new PeerHolder(activity, parent); 64 | } 65 | 66 | @Override 67 | protected void bind(PeerHolder holder, Peer item) { 68 | holder.bind(item); 69 | } 70 | 71 | @Override 72 | protected Peer get(int position) { 73 | return list.get(position); 74 | } 75 | 76 | @Override 77 | protected int getCount() { 78 | Log.v(TAG, "getCount "+list.size()); 79 | return list.size(); 80 | } 81 | 82 | @Override 83 | public void added(Peer obj) { 84 | Log.v(TAG, "Adding "+obj.displayName()); 85 | list.add(obj); 86 | } 87 | 88 | @Override 89 | public void removed(Peer obj) { 90 | Log.v(TAG, "Removing "+obj.displayName()); 91 | list.remove(obj); 92 | } 93 | 94 | @Override 95 | public void updated(Peer obj) { 96 | Log.v(TAG, "Updated "+obj.displayName()); 97 | } 98 | 99 | @Override 100 | public void reset() { 101 | Log.v(TAG, "Reset"); 102 | list.beginBatchedUpdates(); 103 | list.clear(); 104 | list.addAll(identity.messaging.contacts); 105 | list.endBatchedUpdates(); 106 | } 107 | 108 | @Override 109 | public void onVisible() { 110 | int g = identity.messaging.observeContacts.add(this); 111 | if (g != generation) 112 | reset(); 113 | } 114 | 115 | @Override 116 | public void onHidden() { 117 | generation = identity.messaging.observeContacts.remove(this); 118 | } 119 | 120 | @Override 121 | public void onDetach(boolean configChanging) { 122 | 123 | } 124 | 125 | @Override 126 | public ILifecycle onAttach(MainActivity activity, Navigation n, Identity id, Peer peer, Bundle args) { 127 | this.activity = activity; 128 | this.identity = id; 129 | return this; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/feeds/FeedAdapter.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.feeds; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | 9 | import org.servalproject.mid.MessageFeed; 10 | import org.servalproject.servalchat.R; 11 | import org.servalproject.servalchat.navigation.MainActivity; 12 | import org.servalproject.servalchat.views.BasicViewHolder; 13 | import org.servalproject.servalchat.views.ScrollingAdapter; 14 | import org.servalproject.servalchat.views.TimestampView; 15 | import org.servalproject.servaldna.meshmb.PlyMessage; 16 | 17 | /** 18 | * Created by jeremy on 8/08/16. 19 | */ 20 | public abstract class FeedAdapter extends ScrollingAdapter { 21 | public FeedAdapter(MessageFeed feed) { 22 | super(feed, R.string.empty_feed); 23 | } 24 | 25 | @Override 26 | public MessageHolder create(ViewGroup parent, int viewType) { 27 | LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 28 | return new MessageHolder(inflater.inflate(R.layout.feed_message, parent, false)); 29 | } 30 | 31 | @Override 32 | protected void bind(MessageHolder holder, PlyMessage item) { 33 | } 34 | 35 | @Override 36 | protected void bindItem(MessageHolder holder, int position) { 37 | holder.bind(getItem(position), position>0?getItem(position - 1):null); 38 | } 39 | 40 | @Override 41 | public void insertedItem(PlyMessage item, int position) { 42 | super.insertedItem(item, position); 43 | if (position+1 { 28 | 29 | private final PublicFeedsPresenter presenter; 30 | private HashSet bundles = new HashSet<>(); 31 | 32 | public FeedListAdapter(FeedList list, PublicFeedsPresenter presenter) { 33 | super(list, R.string.empty_feed_list); 34 | this.presenter = presenter; 35 | } 36 | 37 | private void removeItem(BundleId id) { 38 | // find the old item and remove it. 39 | for (int i = 0; i < items.size(); i++) { 40 | if (items.get(i).manifest.id.equals(id)) { 41 | items.remove(i); 42 | return; 43 | } 44 | } 45 | } 46 | 47 | @Override 48 | protected void addItem(int index, RhizomeListBundle item) { 49 | if (item.author == null && item.manifest.sender == null) 50 | return; 51 | Subscriber subscriber = new Subscriber( 52 | item.author != null ? item.author : item.manifest.sender, 53 | item.manifest.id, true); 54 | BundleId id = item.manifest.id; 55 | Messaging.SubscriptionState state = presenter.identity.messaging.getSubscriptionState(subscriber); 56 | if (state == Messaging.SubscriptionState.Blocked) 57 | return; 58 | if (bundles.contains(id)) { 59 | if (index == items.size()) 60 | return; 61 | removeItem(id); 62 | }else{ 63 | bundles.add(id); 64 | } 65 | super.addItem(index, item); 66 | } 67 | 68 | @Override 69 | protected MainActivity getActivity() { 70 | return presenter.getActivity(); 71 | } 72 | 73 | @Override 74 | protected void bind(FeedHolder holder, RhizomeListBundle item) { 75 | holder.bind(item); 76 | } 77 | 78 | @Override 79 | public FeedHolder create(ViewGroup parent, int viewType) { 80 | LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 81 | return new FeedHolder(inflater.inflate(R.layout.feed, parent, false)); 82 | } 83 | 84 | public class FeedHolder extends BasicViewHolder implements View.OnClickListener { 85 | private TextView name; 86 | private ImageView icon; 87 | private Subscriber subscriber; 88 | 89 | public FeedHolder(View itemView) { 90 | super(itemView); 91 | this.name = (TextView) this.itemView.findViewById(R.id.name); 92 | this.icon = (ImageView) this.itemView.findViewById(R.id.identicon); 93 | this.itemView.setOnClickListener(this); 94 | } 95 | 96 | public void bind(RhizomeListBundle item) { 97 | subscriber = new Subscriber( 98 | item.author != null ? item.author : item.manifest.sender, 99 | item.manifest.id, true); 100 | this.icon.setImageDrawable(new Identicon(item.manifest.id)); 101 | if (item.manifest.name == null || "".equals(item.manifest.name)) 102 | name.setText(subscriber.sid.abbreviation()); 103 | else 104 | name.setText(item.manifest.name); 105 | } 106 | 107 | @Override 108 | public void onClick(View v) { 109 | presenter.openFeed(subscriber); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/feeds/MyFeed.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.feeds; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.EditText; 10 | import android.widget.RelativeLayout; 11 | 12 | import org.servalproject.mid.Identity; 13 | import org.servalproject.mid.Peer; 14 | import org.servalproject.servalchat.R; 15 | import org.servalproject.servalchat.navigation.ILifecycle; 16 | import org.servalproject.servalchat.navigation.INavigate; 17 | import org.servalproject.servalchat.navigation.MainActivity; 18 | import org.servalproject.servalchat.navigation.Navigation; 19 | import org.servalproject.servalchat.views.RecyclerHelper; 20 | 21 | /** 22 | * Created by jeremy on 8/08/16. 23 | */ 24 | public class MyFeed extends RelativeLayout 25 | implements INavigate, View.OnClickListener { 26 | Button post; 27 | EditText message; 28 | RecyclerView list; 29 | MyFeedPresenter presenter; 30 | MainActivity activity; 31 | 32 | public MyFeed(Context context, AttributeSet attrs) { 33 | super(context, attrs); 34 | } 35 | 36 | @Override 37 | public ILifecycle onAttach(MainActivity activity, Navigation n, Identity id, Peer peer, Bundle args) { 38 | this.activity = activity; 39 | this.post = (Button) findViewById(R.id.post); 40 | this.post.setOnClickListener(this); 41 | this.message = (EditText) findViewById(R.id.message); 42 | this.list = (RecyclerView) findViewById(R.id.activity); 43 | RecyclerHelper.createLayoutManager(list, true, false); 44 | RecyclerHelper.createDivider(list); 45 | return presenter = MyFeedPresenter.factory.getPresenter(this, id, peer, args); 46 | } 47 | 48 | @Override 49 | public void onClick(View v) { 50 | switch (v.getId()) { 51 | case R.id.post: 52 | presenter.post(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/feeds/MyFeedPresenter.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.feeds; 2 | 3 | import android.os.Bundle; 4 | 5 | import org.servalproject.mid.Identity; 6 | import org.servalproject.mid.IdentityFeed; 7 | import org.servalproject.mid.Peer; 8 | import org.servalproject.mid.Serval; 9 | import org.servalproject.servalchat.App; 10 | import org.servalproject.servalchat.navigation.MainActivity; 11 | import org.servalproject.servalchat.navigation.Navigation; 12 | import org.servalproject.servalchat.views.BackgroundWorker; 13 | import org.servalproject.servalchat.views.Presenter; 14 | import org.servalproject.servalchat.views.PresenterFactory; 15 | import org.servalproject.servaldna.Subscriber; 16 | 17 | /** 18 | * Created by jeremy on 8/08/16. 19 | */ 20 | public class MyFeedPresenter extends Presenter { 21 | private final IdentityFeed feed; 22 | private ActivityAdapter adapter; 23 | 24 | protected MyFeedPresenter(PresenterFactory factory, String key, Identity identity) { 25 | super(factory, key, identity); 26 | this.feed = identity.getFeed(); 27 | } 28 | 29 | public static final PresenterFactory factory = new PresenterFactory() { 30 | @Override 31 | protected MyFeedPresenter create(String key, Identity id, Peer peer) { 32 | return new MyFeedPresenter(this, key, id); 33 | } 34 | }; 35 | 36 | private boolean posting = false; 37 | 38 | private void setEnabled() { 39 | MyFeed view = getView(); 40 | if (view == null) 41 | return; 42 | view.post.setEnabled(!posting); 43 | view.message.setEnabled(!posting); 44 | } 45 | 46 | @Override 47 | protected void restore(Bundle config) { 48 | super.restore(config); 49 | adapter = new ActivityAdapter(identity.getActivity(), this); 50 | } 51 | 52 | @Override 53 | protected void bind(MyFeed view) { 54 | view.list.setAdapter(adapter); 55 | setEnabled(); 56 | if (App.isTesting() && "".equals(view.message.getText().toString())) 57 | view.message.setText("Sample Post \uD83D\uDE00"); 58 | } 59 | 60 | public void post() { 61 | MyFeed view = getView(); 62 | if (view == null) 63 | return; 64 | final String message = view.message.getText().toString(); 65 | if ("".equals(message)) 66 | return; 67 | 68 | posting = true; 69 | setEnabled(); 70 | new BackgroundWorker() { 71 | @Override 72 | protected void onBackGround() throws Exception { 73 | feed.sendMessage(message); 74 | } 75 | 76 | @Override 77 | protected void onComplete(Throwable t) { 78 | posting = false; 79 | MyFeed view = getView(); 80 | if (view != null) { 81 | if (t == null) 82 | view.message.setText(""); 83 | else 84 | view.activity.showError(t); 85 | setEnabled(); 86 | }else 87 | rethrow(t); 88 | } 89 | }.execute(); 90 | } 91 | 92 | public void openFeed(Subscriber subscriber, long offset) { 93 | MyFeed view = getView(); 94 | if (view == null || subscriber.equals(identity.subscriber)) 95 | return; 96 | Peer peer = Serval.getInstance().knownPeers.getPeer(subscriber); 97 | Bundle args = new Bundle(); 98 | args.putLong("offset", offset); 99 | view.activity.go(Navigation.PeerFeed, peer, args); 100 | } 101 | 102 | protected MainActivity getActivity() { 103 | MyFeed view = getView(); 104 | return view == null ? null : view.activity; 105 | } 106 | 107 | 108 | @Override 109 | public void onVisible() { 110 | super.onVisible(); 111 | adapter.onVisible(); 112 | } 113 | 114 | @Override 115 | public void onHidden() { 116 | super.onHidden(); 117 | adapter.onHidden(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/feeds/PeerFeed.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.feeds; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.design.widget.Snackbar; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.util.AttributeSet; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.widget.LinearLayout; 13 | 14 | import org.servalproject.mid.Identity; 15 | import org.servalproject.mid.Messaging; 16 | import org.servalproject.mid.Peer; 17 | import org.servalproject.servalchat.R; 18 | import org.servalproject.servalchat.navigation.IHaveMenu; 19 | import org.servalproject.servalchat.navigation.ILifecycle; 20 | import org.servalproject.servalchat.navigation.INavigate; 21 | import org.servalproject.servalchat.navigation.MainActivity; 22 | import org.servalproject.servalchat.navigation.Navigation; 23 | import org.servalproject.servalchat.views.RecyclerHelper; 24 | import org.servalproject.servaldna.meshmb.MeshMBCommon; 25 | 26 | /** 27 | * Created by jeremy on 3/08/16. 28 | */ 29 | public class PeerFeed extends LinearLayout 30 | implements INavigate { 31 | 32 | RecyclerView list; 33 | PeerFeedPresenter presenter; 34 | MainActivity activity; 35 | 36 | public PeerFeed(Context context, @Nullable AttributeSet attrs) { 37 | super(context, attrs); 38 | } 39 | 40 | @Override 41 | public ILifecycle onAttach(MainActivity activity, Navigation n, Identity id, Peer peer, Bundle args) { 42 | this.activity = activity; 43 | this.list = (RecyclerView) findViewById(R.id.list); 44 | RecyclerHelper.createLayoutManager(list, true, false); 45 | return presenter = PeerFeedPresenter.factory.getPresenter(this, id, peer, args); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/feeds/PeerFeedPresenter.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.feeds; 2 | 3 | import android.os.Bundle; 4 | 5 | import org.servalproject.mid.Identity; 6 | import org.servalproject.mid.MessageFeed; 7 | import org.servalproject.mid.Observer; 8 | import org.servalproject.mid.Peer; 9 | import org.servalproject.servalchat.navigation.MainActivity; 10 | import org.servalproject.servalchat.views.Presenter; 11 | import org.servalproject.servalchat.views.PresenterFactory; 12 | 13 | /** 14 | * Created by jeremy on 3/08/16. 15 | */ 16 | public class PeerFeedPresenter extends Presenter { 17 | 18 | private FeedAdapter adapter; 19 | private final Peer peer; 20 | private MessageFeed feed; 21 | 22 | private Observer peerObserver = new Observer() { 23 | @Override 24 | public void updated(Peer obj) { 25 | // if we discover a peer signing key, reset our adapter 26 | if (obj.getSubscriber().signingKey != null && (feed == null || feed.getId() == null)){ 27 | feed = peer.getFeed(); 28 | adapter = new FeedAdapter(feed) { 29 | @Override 30 | protected MainActivity getActivity() { 31 | return PeerFeedPresenter.this.getActivity(); 32 | } 33 | }; 34 | PeerFeed view = getView(); 35 | if (view != null) { 36 | view.list.setAdapter(adapter); 37 | view.activity.supportInvalidateOptionsMenu(); 38 | } 39 | } 40 | } 41 | }; 42 | 43 | protected PeerFeedPresenter(PresenterFactory factory, String key, Identity identity, Peer peer) { 44 | super(factory, key, identity); 45 | this.peer = peer; 46 | } 47 | 48 | public static PresenterFactory factory 49 | = new PresenterFactory() { 50 | 51 | @Override 52 | protected PeerFeedPresenter create(String key, Identity id, Peer peer) { 53 | return new PeerFeedPresenter(this, key, id, peer); 54 | } 55 | 56 | }; 57 | 58 | @Override 59 | protected void bind(PeerFeed feed) { 60 | feed.list.setAdapter(adapter); 61 | } 62 | 63 | @Override 64 | protected void restore(Bundle config) { 65 | feed = peer.getFeed(); 66 | adapter = new FeedAdapter(feed) { 67 | @Override 68 | protected MainActivity getActivity() { 69 | return PeerFeedPresenter.this.getActivity(); 70 | } 71 | }; 72 | } 73 | 74 | protected MainActivity getActivity() { 75 | PeerFeed view = getView(); 76 | return view == null ? null : view.activity; 77 | } 78 | 79 | @Override 80 | public void onVisible() { 81 | super.onVisible(); 82 | adapter.onVisible(); 83 | peer.observers.addUI(this.peerObserver); 84 | } 85 | 86 | @Override 87 | public void onHidden() { 88 | super.onHidden(); 89 | adapter.onHidden(); 90 | peer.observers.removeUI(this.peerObserver); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/feeds/PublicFeedsList.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.feeds; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.text.Editable; 9 | import android.text.TextWatcher; 10 | import android.util.AttributeSet; 11 | import android.widget.EditText; 12 | import android.widget.RelativeLayout; 13 | 14 | import org.servalproject.mid.Identity; 15 | import org.servalproject.mid.Peer; 16 | import org.servalproject.servalchat.R; 17 | import org.servalproject.servalchat.navigation.ILifecycle; 18 | import org.servalproject.servalchat.navigation.INavigate; 19 | import org.servalproject.servalchat.navigation.MainActivity; 20 | import org.servalproject.servalchat.navigation.Navigation; 21 | import org.servalproject.servalchat.views.RecyclerHelper; 22 | 23 | /** 24 | * Created by jeremy on 11/10/16. 25 | */ 26 | public class PublicFeedsList extends RelativeLayout implements INavigate { 27 | PublicFeedsPresenter presenter; 28 | MainActivity activity; 29 | EditText search; 30 | RecyclerView feedList; 31 | 32 | public PublicFeedsList(Context context, @Nullable AttributeSet attrs) { 33 | super(context, attrs); 34 | } 35 | 36 | @Override 37 | public ILifecycle onAttach(MainActivity activity, Navigation n, Identity id, Peer peer, Bundle args) { 38 | this.activity = activity; 39 | search = findViewById(R.id.search); 40 | feedList = findViewById(R.id.feed_list); 41 | search.addTextChangedListener(new TextWatcher() { 42 | @Override 43 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 44 | 45 | } 46 | 47 | @Override 48 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 49 | presenter.search(charSequence); 50 | } 51 | 52 | @Override 53 | public void afterTextChanged(Editable editable) { 54 | 55 | } 56 | }); 57 | RecyclerHelper.createLayoutManager(feedList, true, false); 58 | RecyclerHelper.createDivider(feedList); 59 | presenter = PublicFeedsPresenter.factory.getPresenter(this, id, peer, args); 60 | return presenter; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/feeds/PublicFeedsPresenter.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.feeds; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | 6 | import org.servalproject.mid.FeedList; 7 | import org.servalproject.mid.Identity; 8 | import org.servalproject.mid.KnownPeers; 9 | import org.servalproject.mid.Peer; 10 | import org.servalproject.mid.Serval; 11 | import org.servalproject.servalchat.navigation.MainActivity; 12 | import org.servalproject.servalchat.navigation.Navigation; 13 | import org.servalproject.servalchat.views.Presenter; 14 | import org.servalproject.servalchat.views.PresenterFactory; 15 | import org.servalproject.servaldna.Subscriber; 16 | 17 | /** 18 | * Created by jeremy on 11/10/16. 19 | */ 20 | public class PublicFeedsPresenter extends Presenter { 21 | FeedList list; 22 | FeedListAdapter adapter; 23 | 24 | private static final String TAG = "PublicFeedsPresenter"; 25 | 26 | protected PublicFeedsPresenter(PresenterFactory factory, String key, Identity identity) { 27 | super(factory, key, identity); 28 | } 29 | 30 | public static PresenterFactory factory 31 | = new PresenterFactory() { 32 | 33 | @Override 34 | protected PublicFeedsPresenter create(String key, Identity id, Peer peer) { 35 | return new PublicFeedsPresenter(this, key, id); 36 | } 37 | }; 38 | 39 | @Override 40 | protected void bind(PublicFeedsList view) { 41 | view.feedList.setAdapter(adapter); 42 | } 43 | 44 | @Override 45 | protected void restore(Bundle config) { 46 | list = identity.getAllFeeds(null); 47 | adapter = new FeedListAdapter(list, this); 48 | } 49 | 50 | public void openFeed(Subscriber subscriber) { 51 | PublicFeedsList view = getView(); 52 | if (view == null) 53 | return; 54 | if (identity.subscriber.equals(subscriber)) { 55 | view.activity.go(Navigation.MyFeed); 56 | } else { 57 | Peer peer = Serval.getInstance().knownPeers.getPeer(subscriber); 58 | view.activity.go(Navigation.PeerFeed, peer, null); 59 | } 60 | } 61 | 62 | protected MainActivity getActivity() { 63 | PublicFeedsList view = getView(); 64 | return view == null ? null : view.activity; 65 | } 66 | 67 | @Override 68 | public void onVisible() { 69 | super.onVisible(); 70 | adapter.onVisible(); 71 | } 72 | 73 | @Override 74 | public void onHidden() { 75 | super.onHidden(); 76 | adapter.onHidden(); 77 | } 78 | 79 | public void search(CharSequence search) { 80 | String srch = (search == null || search.equals("")) ? null : "%"+search+"%"; 81 | if (list.search == null && srch == null) 82 | return; 83 | if (srch != null && srch.equals(list.search)) 84 | return; 85 | 86 | Log.v(TAG, "Replacing adapter for "+srch); 87 | FeedList list = identity.getAllFeeds(srch); 88 | FeedListAdapter adapter = new FeedListAdapter(list, this); 89 | getView().feedList.setAdapter(adapter); 90 | this.adapter.clear(); 91 | this.list = list; 92 | this.adapter = adapter; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/identity/IdentityDetails.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.identity; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.widget.Button; 8 | import android.widget.EditText; 9 | import android.widget.ImageView; 10 | import android.widget.LinearLayout; 11 | import android.widget.TextView; 12 | 13 | import org.servalproject.mid.Identity; 14 | import org.servalproject.mid.Peer; 15 | import org.servalproject.servalchat.R; 16 | import org.servalproject.servalchat.navigation.ILifecycle; 17 | import org.servalproject.servalchat.navigation.INavigate; 18 | import org.servalproject.servalchat.navigation.MainActivity; 19 | import org.servalproject.servalchat.navigation.Navigation; 20 | 21 | /** 22 | * Created by jeremy on 7/06/16. 23 | */ 24 | public class IdentityDetails extends LinearLayout 25 | implements INavigate, View.OnClickListener { 26 | MainActivity activity; 27 | ImageView icon; 28 | TextView sidLabel; 29 | TextView sid; 30 | EditText name; 31 | Button update; 32 | IdentityDetailsPresenter presenter; 33 | 34 | public IdentityDetails(Context context, AttributeSet attrs) { 35 | super(context, attrs); 36 | } 37 | 38 | @Override 39 | public ILifecycle onAttach(MainActivity activity, Navigation n, Identity id, Peer peer, Bundle args) { 40 | this.activity = activity; 41 | icon = (ImageView) findViewById(R.id.identicon); 42 | sidLabel = (TextView) findViewById(R.id.sid_label); 43 | sid = (TextView) findViewById(R.id.sid); 44 | name = (EditText) findViewById(R.id.name); 45 | update = (Button) findViewById(R.id.update); 46 | update.setOnClickListener(this); 47 | 48 | presenter = IdentityDetailsPresenter.factory.getPresenter(this, id, peer, args); 49 | return presenter; 50 | } 51 | 52 | @Override 53 | public void onClick(View v) { 54 | switch (v.getId()) { 55 | case R.id.update: 56 | presenter.update(); 57 | } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/identity/IdentityDetailsPresenter.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.identity; 2 | 3 | import android.view.View; 4 | 5 | import org.servalproject.mid.Identity; 6 | import org.servalproject.mid.Peer; 7 | import org.servalproject.mid.Serval; 8 | import org.servalproject.servalchat.App; 9 | import org.servalproject.servalchat.R; 10 | import org.servalproject.servalchat.navigation.Navigation; 11 | import org.servalproject.servalchat.views.BackgroundWorker; 12 | import org.servalproject.servalchat.views.Presenter; 13 | import org.servalproject.servalchat.views.PresenterFactory; 14 | 15 | /** 16 | * Created by jeremy on 20/07/16. 17 | */ 18 | public class IdentityDetailsPresenter extends Presenter { 19 | 20 | public static PresenterFactory factory 21 | = new PresenterFactory() { 22 | @Override 23 | protected IdentityDetailsPresenter create(String key, Identity id, Peer peer) { 24 | return new IdentityDetailsPresenter(this, key, id); 25 | } 26 | }; 27 | 28 | private boolean updating = false; 29 | 30 | private IdentityDetailsPresenter(PresenterFactory factory, String key, Identity id) { 31 | super(factory, key, id); 32 | } 33 | 34 | @Override 35 | protected void bind(IdentityDetails view) { 36 | if (identity != null) { 37 | view.name.setText(identity.getName()); 38 | view.sid.setText(identity.subscriber.sid.toHex()); 39 | view.icon.setImageDrawable(identity.getIcon()); 40 | view.update.setText(R.string.identity_update); 41 | view.sidLabel.setVisibility(View.VISIBLE); 42 | view.sid.setVisibility(View.VISIBLE); 43 | view.icon.setVisibility(View.VISIBLE); 44 | } else { 45 | view.sidLabel.setVisibility(View.GONE); 46 | view.sid.setVisibility(View.GONE); 47 | view.icon.setVisibility(View.GONE); 48 | view.update.setText(R.string.add_identity); 49 | if (App.isTesting()) 50 | view.name.setText("Test User"); 51 | } 52 | view.update.setEnabled(!updating); 53 | } 54 | 55 | @Override 56 | public void onVisible() { 57 | IdentityDetails view = getView(); 58 | if (view != null) 59 | bind(view); 60 | } 61 | 62 | public void update() { 63 | if (updating) 64 | return; 65 | 66 | IdentityDetails view = getView(); 67 | if (view == null) 68 | return; 69 | 70 | updating = true; 71 | view.update.setEnabled(!updating); 72 | final String name = view.name.getText().toString(); 73 | 74 | new BackgroundWorker() { 75 | private Identity result; 76 | 77 | @Override 78 | protected void onBackGround() throws Exception { 79 | Serval serval = Serval.getInstance(); 80 | if (identity == null) { 81 | result = serval.identities.addIdentity("", name, ""); 82 | } else { 83 | serval.identities.updateIdentity( 84 | identity, "", name, ""); 85 | result = identity; 86 | } 87 | } 88 | 89 | @Override 90 | protected void onComplete(Throwable t) { 91 | updating = false; 92 | IdentityDetails view = getView(); 93 | if (t != null) { 94 | if (view == null) { 95 | rethrow(t); 96 | } else { 97 | view.activity.showError(t); 98 | } 99 | } else if (view != null && view.activity != null){ 100 | if (identity == null) 101 | view.activity.go(Navigation.MyFeed, result, null, null, true); 102 | else 103 | view.activity.go(Navigation.MyFeed); 104 | } 105 | } 106 | }.execute(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/CountTitle.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | import android.graphics.drawable.Drawable; 4 | 5 | import org.servalproject.mid.Messaging; 6 | import org.servalproject.mid.ObserverProxy; 7 | import org.servalproject.mid.ObserverSet; 8 | import org.servalproject.mid.Serval; 9 | import org.servalproject.servalchat.navigation.NavTitle; 10 | 11 | public abstract class CountTitle extends NavTitle { 12 | private final CharSequence prefix; 13 | 14 | public CountTitle(ObserverSet observer, CharSequence prefix) { 15 | observers = new ObserverProxy<>(Serval.getInstance().uiHandler, observer, this); 16 | this.prefix = prefix; 17 | } 18 | 19 | @Override 20 | public Drawable getIcon() { 21 | return null; 22 | } 23 | 24 | public abstract int unreadCount(); 25 | 26 | @Override 27 | public CharSequence getMenuLabel() { 28 | return prefix+" ("+unreadCount()+")"; 29 | } 30 | 31 | @Override 32 | public CharSequence getTitle() { 33 | return prefix; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/HistoryItem.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | import android.os.Bundle; 4 | 5 | import org.servalproject.servaldna.SigningKey; 6 | import org.servalproject.servaldna.Subscriber; 7 | 8 | /** 9 | * Created by jeremy on 20/07/16. 10 | */ 11 | public class HistoryItem { 12 | public final Navigation key; 13 | public final SigningKey identity; 14 | public final Subscriber peer; 15 | public final Bundle args; 16 | 17 | public HistoryItem(Navigation key, SigningKey identity, Subscriber peer, Bundle args) { 18 | this.key = key; 19 | this.args = args; 20 | this.identity = identity; 21 | this.peer = peer; 22 | } 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) return true; 27 | if (o == null || getClass() != o.getClass()) return false; 28 | 29 | HistoryItem that = (HistoryItem) o; 30 | 31 | // TODO compare arg values? 32 | return key.equals(that.key) && args == that.args; 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return key.hashCode(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/IContainerView.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | import android.os.Bundle; 4 | 5 | import org.servalproject.mid.Identity; 6 | import org.servalproject.mid.Peer; 7 | 8 | /** 9 | * Created by jeremy on 14/06/16. 10 | */ 11 | public interface IContainerView { 12 | void deactivate(ViewState state, boolean configChange, boolean visible); 13 | ViewState activate(Navigation n, Identity identity, Peer peer, Bundle args, boolean visible); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/IHaveMenu.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | import android.view.Menu; 4 | 5 | /** 6 | * Created by jeremy on 20/06/16. 7 | */ 8 | public interface IHaveMenu { 9 | void populateItems(Menu menu); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/ILifecycle.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | /** 4 | * Created by jeremy on 14/06/16. 5 | */ 6 | public interface ILifecycle { 7 | 8 | void onDetach(boolean configChange); 9 | 10 | void onVisible(); 11 | 12 | void onHidden(); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/INavigate.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | import android.os.Bundle; 4 | 5 | import org.servalproject.mid.Identity; 6 | import org.servalproject.mid.Peer; 7 | 8 | /** 9 | * Created by jeremy on 14/06/16. 10 | */ 11 | public interface INavigate { 12 | ILifecycle onAttach(MainActivity activity, Navigation n, Identity id, Peer peer, Bundle args); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/INavigatorHost.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Created by jeremy on 15/06/16. 7 | */ 8 | public interface INavigatorHost extends IContainerView { 9 | void navigated(boolean backEnabled, boolean upEnabled); 10 | 11 | void rebuildMenu(); 12 | 13 | void showSnack(CharSequence message, int length, CharSequence actionLabel, View.OnClickListener action); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/IOnBack.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | /** 4 | * Created by jeremy on 6/11/17. 5 | */ 6 | 7 | public interface IOnBack { 8 | boolean onBack(); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/IRootContainer.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | import android.support.design.widget.CoordinatorLayout; 4 | import android.support.v7.widget.Toolbar; 5 | import android.view.MenuItem; 6 | 7 | /** 8 | * Created by jeremy on 6/11/17. 9 | */ 10 | 11 | public interface IRootContainer extends IContainerView{ 12 | Toolbar getToolbar(); 13 | CoordinatorLayout getCoordinator(); 14 | void updateToolbar(boolean canGoBack); 15 | boolean onOptionsItemSelected(MenuItem item); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/Id1.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | /** 4 | * Created by jeremy on 25/07/16. 5 | */ 6 | public class Id1 extends MainActivity { 7 | // this class only exists so we can have multiple tasks pre SDK 21 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/Id2.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | /** 4 | * Created by jeremy on 25/07/16. 5 | */ 6 | public class Id2 extends MainActivity { 7 | // this class only exists so we can have multiple tasks pre SDK 21 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/Id3.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | /** 4 | * Created by jeremy on 25/07/16. 5 | */ 6 | public class Id3 extends MainActivity { 7 | // this class only exists so we can have multiple tasks pre SDK 21 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/Id4.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | /** 4 | * Created by jeremy on 25/07/16. 5 | */ 6 | public class Id4 extends MainActivity { 7 | // this class only exists so we can have multiple tasks pre SDK 21 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/NavTabStrip.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.design.widget.TabLayout; 6 | import android.support.v4.view.ViewPager; 7 | import android.util.AttributeSet; 8 | import android.widget.LinearLayout; 9 | 10 | import org.servalproject.mid.Identity; 11 | import org.servalproject.mid.Peer; 12 | import org.servalproject.servalchat.R; 13 | import org.servalproject.servaldna.SigningKey; 14 | import org.servalproject.servaldna.Subscriber; 15 | 16 | /** 17 | * Created by jeremy on 7/06/16. 18 | */ 19 | public class NavTabStrip extends LinearLayout implements IContainerView, INavigate { 20 | NavPageAdapter adapter; 21 | private ViewPager pager; 22 | 23 | public NavTabStrip(Context context, AttributeSet attrs) { 24 | super(context, attrs); 25 | } 26 | 27 | @Override 28 | public void deactivate(ViewState viewState, boolean configChange, boolean visible) { 29 | // Noop, handled by viewpager 30 | } 31 | 32 | @Override 33 | public ViewState activate(Navigation n, Identity identity, Peer peer, Bundle args, boolean visible) { 34 | for (int i = 0; i < adapter.screens.length; i++) { 35 | HistoryItem screen = adapter.screens[i]; 36 | if (screen.key.equals(n)) { 37 | if (pager.getCurrentItem() != i) 38 | pager.setCurrentItem(i); 39 | return adapter.getViewState(i); 40 | } 41 | } 42 | throw new IllegalStateException("Cannot locate " + n.name); 43 | } 44 | 45 | @Override 46 | public ILifecycle onAttach(MainActivity activity, Navigation n, Identity id, Peer peer, Bundle args) { 47 | HistoryItem items[] = new HistoryItem[n.children.size()]; 48 | // use the same arguments for all tabs 49 | SigningKey key = id == null ? null : id.subscriber.signingKey; 50 | Subscriber subscriber = peer == null ? null : peer.getSubscriber(); 51 | for (int i = 0; i < n.children.size(); i++) 52 | items[i] = new HistoryItem(n.children.get(i), key, subscriber, args); 53 | adapter = new NavPageAdapter(activity, id, peer, items); 54 | pager = (ViewPager) findViewById(R.id.pager); 55 | adapter.setViewPager(pager); 56 | TabLayout tabs = (TabLayout) findViewById(R.id.sliding_tabs); 57 | tabs.setupWithViewPager(pager); 58 | return adapter; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/NavTitle.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | import android.graphics.drawable.Drawable; 4 | 5 | import org.servalproject.mid.IObserverSet; 6 | 7 | public abstract class NavTitle { 8 | protected IObserverSet observers; 9 | 10 | public abstract Drawable getIcon(); 11 | 12 | public CharSequence getMenuLabel(){ 13 | return getTitle(); 14 | } 15 | 16 | public abstract CharSequence getTitle(); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/RootAppbar.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | import android.app.ActivityManager; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.support.design.widget.CoordinatorLayout; 8 | import android.support.v7.app.ActionBar; 9 | import android.support.v7.widget.Toolbar; 10 | import android.util.AttributeSet; 11 | import android.view.MenuItem; 12 | import android.view.ViewGroup; 13 | import android.widget.LinearLayout; 14 | 15 | import org.servalproject.mid.Identity; 16 | import org.servalproject.mid.Peer; 17 | import org.servalproject.servalchat.R; 18 | 19 | /** 20 | * Created by jeremy on 6/11/17. 21 | */ 22 | 23 | public class RootAppbar extends CoordinatorLayout implements IRootContainer, INavigate { 24 | private ViewGroup rootLayout; 25 | Toolbar toolbar; 26 | private MainActivity activity; 27 | private static final String TAG = "RootAppbar"; 28 | 29 | public RootAppbar(Context context, AttributeSet attrs) { 30 | this(context, attrs, android.R.attr.dialogPreferenceStyle); 31 | } 32 | 33 | public RootAppbar(Context context, AttributeSet attrs, int defStyleAttr) { 34 | super(context, attrs, defStyleAttr); 35 | } 36 | 37 | @Override 38 | public void deactivate(ViewState state, boolean configChange, boolean visible) { 39 | for(ILifecycle l : state.getLifecycle()) { 40 | if (visible) 41 | l.onHidden(); 42 | l.onDetach(configChange); 43 | } 44 | rootLayout.removeView(state.view); 45 | } 46 | 47 | @Override 48 | public ViewState activate(Navigation n, Identity identity, Peer peer, Bundle args, boolean visible) { 49 | ViewState ret = ViewState.Inflate(activity, n, identity, peer, args); 50 | ret.view.setLayoutParams(new LinearLayout.LayoutParams( 51 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 52 | rootLayout.addView(ret.view); 53 | for(ILifecycle l : ret.getLifecycle()) 54 | l.onVisible(); 55 | 56 | activity.setSupportActionBar(toolbar); 57 | NavTitle title = n.getTitle(activity, identity, peer); 58 | toolbar.setTitle(title.getTitle()); 59 | if (Build.VERSION.SDK_INT>=21) { 60 | activity.setTaskDescription(new ActivityManager.TaskDescription(title.toString(), identity == null ? null : identity.getBitmap())); 61 | } 62 | 63 | return ret; 64 | } 65 | 66 | @Override 67 | public ILifecycle onAttach(MainActivity activity, Navigation n, Identity id, Peer peer, Bundle args) { 68 | this.activity = activity; 69 | rootLayout = (ViewGroup) findViewById(R.id.root_layout); 70 | toolbar = (Toolbar) findViewById(R.id.app_toolbar); 71 | return null; 72 | } 73 | 74 | @Override 75 | public Toolbar getToolbar() { 76 | return toolbar; 77 | } 78 | 79 | @Override 80 | public CoordinatorLayout getCoordinator() { 81 | return (CoordinatorLayout)findViewById(R.id.coordinator); 82 | } 83 | 84 | @Override 85 | public void updateToolbar(boolean canGoBack) { 86 | ActionBar bar = activity.getSupportActionBar(); 87 | bar.setDisplayOptions( 88 | ActionBar.DISPLAY_SHOW_HOME | (canGoBack ? ActionBar.DISPLAY_HOME_AS_UP : 0), 89 | ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_HOME_AS_UP); 90 | } 91 | 92 | @Override 93 | public boolean onOptionsItemSelected(MenuItem item) { 94 | switch (item.getItemId()) { 95 | case android.R.id.home: 96 | return activity.goBack(); 97 | } 98 | return false; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/navigation/ViewState.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.navigation; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import org.servalproject.mid.Identity; 9 | import org.servalproject.mid.Peer; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * Created by jeremy on 19/07/16. 16 | */ 17 | public class ViewState { 18 | public final Navigation key; 19 | private IContainerView container; 20 | private List lifecycle = new ArrayList<>(); 21 | public final View view; 22 | private View firstInput = null; 23 | 24 | public IContainerView getContainer() { 25 | return container; 26 | } 27 | 28 | public List getLifecycle() { 29 | return lifecycle; 30 | } 31 | 32 | public View getTextInput(){ 33 | return firstInput; 34 | } 35 | 36 | public interface ViewVisitor{ 37 | boolean visit(View view); 38 | } 39 | 40 | public boolean visitViews(View view, ViewVisitor visitor){ 41 | if (visitor.visit(view)) 42 | return true; 43 | if (view instanceof ViewGroup) { 44 | ViewGroup g = (ViewGroup) view; 45 | for (int i = 0; i < g.getChildCount(); i++) { 46 | if (visitViews(g.getChildAt(i), visitor)) 47 | return true; 48 | } 49 | } 50 | return false; 51 | } 52 | 53 | public boolean visit(ViewVisitor visitor){ 54 | return visitViews(view, visitor); 55 | } 56 | 57 | public static ViewState Inflate(final MainActivity activity, final Navigation key, final Identity identity, final Peer peer, final Bundle args) { 58 | LayoutInflater inflater = LayoutInflater.from(activity); 59 | View view = inflater.inflate(key.layoutResource, null); 60 | final ViewState ret = new ViewState(key, view); 61 | ret.visit(new ViewVisitor() { 62 | @Override 63 | public boolean visit(View view) { 64 | if (view instanceof INavigate) { 65 | INavigate navigate = (INavigate) view; 66 | ILifecycle l = navigate.onAttach(activity, key, identity, peer, args); 67 | if (l!=null) 68 | ret.lifecycle.add(l); 69 | } 70 | if (view.onCheckIsTextEditor() && ret.firstInput == null) 71 | ret.firstInput = view; 72 | if (view instanceof IContainerView && ret.container == null) 73 | ret.container = (IContainerView) view; 74 | return false; 75 | } 76 | }); 77 | return ret; 78 | } 79 | 80 | private ViewState(Navigation key, View view) { 81 | this.key = key; 82 | this.view = view; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/peer/PeerDetails.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.peer; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.design.widget.Snackbar; 6 | import android.util.AttributeSet; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | import android.widget.ImageView; 10 | import android.widget.LinearLayout; 11 | import android.widget.TextView; 12 | 13 | import org.servalproject.mid.Identity; 14 | import org.servalproject.mid.Observer; 15 | import org.servalproject.mid.Peer; 16 | import org.servalproject.mid.Serval; 17 | import org.servalproject.servalchat.R; 18 | import org.servalproject.servalchat.navigation.IHaveMenu; 19 | import org.servalproject.servalchat.navigation.ILifecycle; 20 | import org.servalproject.servalchat.navigation.INavigate; 21 | import org.servalproject.servalchat.navigation.MainActivity; 22 | import org.servalproject.servalchat.navigation.Navigation; 23 | import org.servalproject.servalchat.views.Identicon; 24 | import org.servalproject.servaldna.AbstractId; 25 | import org.servalproject.servaldna.SigningKey; 26 | 27 | /** 28 | * Created by jeremy on 1/08/16. 29 | */ 30 | public class PeerDetails extends LinearLayout 31 | implements INavigate, ILifecycle, Observer { 32 | 33 | private MainActivity activity; 34 | private ImageView icon; 35 | private TextView name; 36 | private TextView number; 37 | private TextView numberLabel; 38 | private TextView sid; 39 | private Peer peer; 40 | 41 | public PeerDetails(Context context, AttributeSet attrs) { 42 | super(context, attrs); 43 | } 44 | 45 | @Override 46 | public ILifecycle onAttach(MainActivity activity, Navigation n, Identity id, Peer peer, Bundle args) { 47 | this.activity = activity; 48 | icon = (ImageView) findViewById(R.id.identicon); 49 | name = (TextView) findViewById(R.id.name); 50 | number = (TextView) findViewById(R.id.number); 51 | numberLabel = (TextView) findViewById(R.id.number_label); 52 | sid = (TextView) findViewById(R.id.sid); 53 | this.peer = peer; 54 | updated(this.peer); 55 | 56 | return this; 57 | } 58 | 59 | @Override 60 | public void onDetach(boolean configChange) { 61 | 62 | } 63 | 64 | @Override 65 | public void onVisible() { 66 | peer.observers.addUI(this); 67 | updated(peer); 68 | } 69 | 70 | @Override 71 | public void onHidden() { 72 | peer.observers.removeUI(this); 73 | } 74 | 75 | @Override 76 | public void updated(Peer obj) { 77 | name.setText(peer.displayName()); 78 | String did =peer.getDid(); 79 | number.setText(did); 80 | SigningKey key = peer.getSubscriber().signingKey; 81 | if (icon.getDrawable() == null && key!=null) 82 | icon.setImageDrawable(new Identicon(key)); 83 | number.setVisibility( (did==null || "".equals(did)) ? GONE : VISIBLE); 84 | numberLabel.setVisibility( (did==null || "".equals(did)) ? GONE : VISIBLE); 85 | 86 | sid.setText(peer.getSubscriber().sid.toHex()); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/peer/PeerHolder.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.peer; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.ImageView; 9 | import android.widget.TextView; 10 | 11 | import org.servalproject.mid.KnownPeers; 12 | import org.servalproject.mid.Peer; 13 | import org.servalproject.servalchat.R; 14 | import org.servalproject.servalchat.navigation.MainActivity; 15 | import org.servalproject.servalchat.navigation.Navigation; 16 | import org.servalproject.servalchat.views.BasicViewHolder; 17 | import org.servalproject.servalchat.views.Identicon; 18 | import org.servalproject.servaldna.SigningKey; 19 | 20 | /** 21 | * Created by jeremy on 30/05/17. 22 | */ 23 | public class PeerHolder extends BasicViewHolder implements View.OnClickListener { 24 | private final ImageView icon; 25 | private final TextView name; 26 | private final MainActivity activity; 27 | private Peer peer; 28 | 29 | public PeerHolder(MainActivity activity, ViewGroup parent) { 30 | super(LayoutInflater.from(parent.getContext()).inflate(R.layout.peer, parent, false)); 31 | this.activity = activity; 32 | //avatar = (ImageView)this.itemView.findViewById(R.id.avatar); 33 | name = (TextView) this.itemView.findViewById(R.id.name); 34 | icon = (ImageView) this.itemView.findViewById(R.id.identicon); 35 | this.itemView.setOnClickListener(this); 36 | } 37 | 38 | public void bind(Peer peer) { 39 | this.peer = peer; 40 | this.name.setText(peer.displayName()); 41 | SigningKey key = peer.getSubscriber().signingKey; 42 | if (key == null){ 43 | this.icon.setVisibility(View.INVISIBLE); 44 | }else{ 45 | this.icon.setImageDrawable(new Identicon(key)); 46 | this.icon.setVisibility(View.VISIBLE); 47 | } 48 | 49 | if (peer.isReachable()) { 50 | // Show green dot? 51 | } 52 | } 53 | 54 | @Override 55 | public void onClick(View v) { 56 | activity.go(Navigation.PeerDetails, peer, null); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/peer/PeerList.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.peer; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | import android.util.AttributeSet; 6 | import android.view.ViewGroup; 7 | 8 | import org.servalproject.mid.ListObserverSet; 9 | import org.servalproject.mid.Peer; 10 | import org.servalproject.mid.Serval; 11 | import org.servalproject.servalchat.R; 12 | import org.servalproject.servalchat.views.ObservedRecyclerView; 13 | import org.servalproject.servalchat.views.RecyclerHelper; 14 | import org.servalproject.servaldna.SubscriberId; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Collections; 18 | import java.util.HashSet; 19 | import java.util.List; 20 | import java.util.Set; 21 | 22 | /** 23 | * Created by jeremy on 31/05/16. 24 | */ 25 | public class PeerList extends ObservedRecyclerView{ 26 | private Serval serval; 27 | private static final String TAG = "PeerList"; 28 | private List items = new ArrayList(); 29 | 30 | private static ListObserverSet getObserver() { 31 | Serval serval = Serval.getInstance(); 32 | if (serval == null) 33 | return null; 34 | return serval.knownPeers.peerListObservers; 35 | } 36 | 37 | public PeerList(Context context, @Nullable AttributeSet attrs) { 38 | super(getObserver(), context, attrs, R.string.empty_peer_list); 39 | listAdapter.setHasStableIds(true); 40 | serval = Serval.getInstance(); 41 | } 42 | 43 | @Override 44 | protected void onFinishInflate() { 45 | super.onFinishInflate(); 46 | setHasFixedSize(true); 47 | RecyclerHelper.createLayoutManager(this, true, false); 48 | RecyclerHelper.createDivider(this); 49 | } 50 | 51 | @Override 52 | public PeerHolder createHolder(ViewGroup parent, int viewType) { 53 | return new PeerHolder(activity, parent); 54 | } 55 | 56 | @Override 57 | public void bind(PeerHolder holder, Peer item) { 58 | holder.bind(item); 59 | } 60 | 61 | @Override 62 | public long getId(Peer item) { 63 | return item.getId(); 64 | } 65 | 66 | private static final int MAP = 1; 67 | 68 | private boolean sorted = false; 69 | private final Set addedPeers = new HashSet<>(); 70 | 71 | @Override 72 | public void onVisible() { 73 | addedPeers.clear(); 74 | items.clear(); 75 | if (serval != null) { 76 | for (Peer p : serval.knownPeers.getReachablePeers()) 77 | add(p); 78 | } 79 | super.onVisible(); 80 | } 81 | 82 | @Override 83 | public void onHidden() { 84 | super.onHidden(); 85 | addedPeers.clear(); 86 | items.clear(); 87 | } 88 | 89 | private boolean add(Peer p) { 90 | if (!p.isReachable()) 91 | return false; 92 | if (addedPeers.contains(p.getSubscriber().sid)) 93 | return false; 94 | addedPeers.add(p.getSubscriber().sid); 95 | items.add(p); 96 | sorted = false; 97 | return true; 98 | } 99 | 100 | @Override 101 | protected Peer get(int position) { 102 | sort(); 103 | return items.get(position); 104 | } 105 | 106 | @Override 107 | protected int getCount() { 108 | return items.size(); 109 | } 110 | 111 | private void sort() { 112 | if (sorted) 113 | return; 114 | Collections.sort(items); 115 | sorted = true; 116 | } 117 | 118 | @Override 119 | public void added(Peer obj) { 120 | if (add(obj)) 121 | super.added(obj); 122 | } 123 | 124 | @Override 125 | public void updated(Peer obj) { 126 | add(obj); 127 | sorted = false; 128 | super.updated(obj); 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/peer/PrivateMessaging.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.peer; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | import android.widget.Button; 10 | import android.widget.EditText; 11 | import android.widget.RelativeLayout; 12 | 13 | import org.servalproject.mid.Identity; 14 | import org.servalproject.mid.Peer; 15 | import org.servalproject.servalchat.R; 16 | import org.servalproject.servalchat.navigation.ILifecycle; 17 | import org.servalproject.servalchat.navigation.INavigate; 18 | import org.servalproject.servalchat.navigation.MainActivity; 19 | import org.servalproject.servalchat.navigation.Navigation; 20 | import org.servalproject.servalchat.views.RecyclerHelper; 21 | 22 | /** 23 | * Created by jeremy on 27/07/16. 24 | */ 25 | public class PrivateMessaging extends RelativeLayout 26 | implements INavigate, View.OnClickListener { 27 | MainActivity activity; 28 | EditText message; 29 | Button send; 30 | RecyclerView list; 31 | PrivateMessagingPresenter presenter; 32 | 33 | public PrivateMessaging(Context context, AttributeSet attrs) { 34 | super(context, attrs); 35 | } 36 | 37 | @Override 38 | public ILifecycle onAttach(MainActivity activity, Navigation n, Identity id, Peer peer, Bundle args) { 39 | this.activity = activity; 40 | this.message = (EditText) findViewById(R.id.message); 41 | this.send = (Button) findViewById(R.id.send); 42 | this.list = (RecyclerView) findViewById(R.id.message_list); 43 | RecyclerHelper.createLayoutManager(list, true, true); 44 | send.setOnClickListener(this); 45 | presenter = PrivateMessagingPresenter.factory.getPresenter(this, id, peer, args); 46 | return presenter; 47 | } 48 | 49 | @Override 50 | public void onClick(View v) { 51 | if (v.getId() == R.id.send) 52 | presenter.send(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/views/BackgroundWorker.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.views; 2 | 3 | import org.servalproject.mid.Serval; 4 | import org.servalproject.servaldna.ServalDInterfaceException; 5 | import org.servalproject.servaldna.meshms.MeshMSException; 6 | 7 | import java.io.IOException; 8 | 9 | /** 10 | * Created by jeremy on 15/11/17. 11 | */ 12 | 13 | // A simple background worker, with simpler exception handling than using AsncTask 14 | public abstract class BackgroundWorker implements Runnable{ 15 | private final Serval serval; 16 | private int state = IDLE; 17 | private Throwable ex; 18 | static final int IDLE=0; 19 | static final int RUNNING=1; 20 | 21 | protected BackgroundWorker(){ 22 | this.serval = Serval.getInstance(); 23 | } 24 | 25 | protected abstract void onBackGround() throws Exception; 26 | protected abstract void onComplete(Throwable t); 27 | 28 | public boolean isRunning(){ 29 | return state == RUNNING; 30 | } 31 | 32 | protected void rethrow(Throwable t){ 33 | if (t == null) 34 | return; 35 | if (t instanceof RuntimeException) 36 | throw (RuntimeException) t; 37 | throw new IllegalStateException(t); 38 | } 39 | 40 | @Override 41 | public void run() { 42 | if (state!=RUNNING) 43 | throw new IllegalStateException(); 44 | if (serval.uiHandler.isOnThread()){ 45 | state = IDLE; 46 | Throwable e = this.ex; 47 | ex = null; 48 | onComplete(e); 49 | }else{ 50 | try { 51 | onBackGround(); 52 | }catch (Throwable t){ 53 | ex = t; 54 | } 55 | serval.uiHandler.post(this); 56 | } 57 | } 58 | 59 | public void execute(){ 60 | if (state != IDLE) 61 | throw new IllegalStateException(); 62 | state = RUNNING; 63 | serval.runOnThreadPool(this); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/views/BasicViewHolder.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.views; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | /** 9 | * Created by jeremy on 30/01/17. 10 | */ 11 | 12 | public abstract class BasicViewHolder extends RecyclerView.ViewHolder { 13 | public BasicViewHolder(View itemView) { 14 | super(itemView); 15 | } 16 | public BasicViewHolder(LayoutInflater inflater, int layout_id, ViewGroup parent){ 17 | this(inflater.inflate(layout_id, parent, false)); 18 | } 19 | public BasicViewHolder(int layout_id, ViewGroup parent){ 20 | this(LayoutInflater.from(parent.getContext()), layout_id, parent); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/views/DisplayError.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.views; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.widget.AppCompatTextView; 7 | import android.util.AttributeSet; 8 | 9 | import org.servalproject.mid.Identity; 10 | import org.servalproject.mid.Peer; 11 | import org.servalproject.servalchat.navigation.ILifecycle; 12 | import org.servalproject.servalchat.navigation.INavigate; 13 | import org.servalproject.servalchat.navigation.MainActivity; 14 | import org.servalproject.servalchat.navigation.Navigation; 15 | 16 | /** 17 | * Created by jeremy on 8/01/18. 18 | */ 19 | 20 | public class DisplayError extends AppCompatTextView implements INavigate { 21 | 22 | public DisplayError(Context context, @Nullable AttributeSet attrs) { 23 | super(context, attrs); 24 | } 25 | 26 | public static final String MESSAGE = "message"; 27 | 28 | @Override 29 | public ILifecycle onAttach(MainActivity activity, Navigation n, Identity id, Peer peer, Bundle args) { 30 | if (args!=null){ 31 | int res = args.getInt(MESSAGE, -1); 32 | if (res!=-1) 33 | setText(res); 34 | } 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/views/FutureList.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.views; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | 5 | import java.util.AbstractList; 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | /** 11 | * Supposed to provide fast random access and grow efficiently from both ends 12 | * notifying an adapter when items are added. 13 | * Created by jeremy on 31/01/17. 14 | */ 15 | 16 | public class FutureList 17 | extends AbstractList{ 18 | 19 | private ScrollingAdapter adapter; 20 | private final List past = new ArrayList<>(); 21 | private final List future = new ArrayList<>(); 22 | 23 | public FutureList(ScrollingAdapter adapter){ 24 | this.adapter = adapter; 25 | } 26 | 27 | public T get(int position){ 28 | if (position<0) 29 | return null; 30 | int futureSize = future.size(); 31 | if (position>=0 && position < futureSize) 32 | return future.get(futureSize - 1 - position); 33 | position -= futureSize; 34 | if (position < past.size()) 35 | return past.get(position); 36 | return null; 37 | } 38 | 39 | @Override 40 | public int size() { 41 | return past.size() + future.size(); 42 | } 43 | 44 | @Override 45 | public void add(int index, T item){ 46 | int futureSize = future.size(); 47 | if (index <= futureSize){ 48 | future.add(futureSize - index, item); 49 | }else{ 50 | past.add(index - futureSize, item); 51 | } 52 | adapter.insertedItem(item, index); 53 | } 54 | 55 | @Override 56 | public boolean add(T item){ 57 | past.add(item); 58 | adapter.insertedItem(item, size() - 1); 59 | return true; 60 | } 61 | 62 | public int find(T item){ 63 | int index = Collections.binarySearch((List>) this, item); 64 | if (index<0) 65 | index = (-index)-1; 66 | return index; 67 | } 68 | 69 | @Override 70 | public T remove(int index) { 71 | T ret = null; 72 | if (index>0){ 73 | int futureSize = future.size(); 74 | if (index>0 && index < futureSize) { 75 | ret = future.remove(futureSize - 1 - index); 76 | }else{ 77 | index -= futureSize; 78 | if (index< past.size()) 79 | ret = past.remove(index); 80 | } 81 | if (ret!=null) 82 | adapter.notifyItemRemoved(index); 83 | } 84 | return ret; 85 | } 86 | 87 | public boolean removeItem(T item) { 88 | int index = Collections.binarySearch((List>) this, item); 89 | if (index<0) 90 | return false; 91 | remove(index); 92 | return true; 93 | } 94 | 95 | @Override 96 | public void clear() { 97 | past.clear(); 98 | future.clear(); 99 | adapter.notifyDataSetChanged(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/views/Identicon.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.views; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.Paint; 8 | import android.graphics.PixelFormat; 9 | import android.graphics.Rect; 10 | import android.graphics.drawable.Drawable; 11 | import android.support.annotation.IntRange; 12 | import android.support.annotation.NonNull; 13 | import android.support.annotation.Nullable; 14 | import android.util.Log; 15 | 16 | import org.servalproject.mid.Identity; 17 | import org.servalproject.servaldna.AbstractId; 18 | 19 | import java.nio.ByteBuffer; 20 | import java.security.MessageDigest; 21 | import java.security.NoSuchAlgorithmException; 22 | 23 | /** 24 | * Created by jeremy on 26/06/17. 25 | */ 26 | 27 | public class Identicon extends Drawable { 28 | private final Bitmap bitPattern; 29 | private final Paint bitmapPaint; 30 | private final Rect dest = new Rect(); 31 | private static final String TAG = "Identicon"; 32 | 33 | public Identicon(AbstractId id){ 34 | this(id.getBinary()); 35 | } 36 | 37 | private static final int patterns[] = new int[]{ 38 | 0b00101, 0b00110, 0b01001, 0b01010, 39 | 0b01011, 0b01100, 0b01101, 0b01110, 40 | 0b10010, 0b10011, 0b10100, 0b10101, 41 | 0b10110, 0b10111, 0b11001, 0b11010 42 | }; 43 | 44 | public Identicon(byte[] value) { 45 | 46 | try { 47 | MessageDigest md = MessageDigest.getInstance("SHA-256"); 48 | md.update(value); 49 | value = md.digest(); 50 | } catch (NoSuchAlgorithmException e) { 51 | throw new IllegalStateException(e); 52 | } 53 | ByteBuffer b = ByteBuffer.wrap(value); 54 | 55 | int H = b.get() & 0xFF; 56 | byte val = b.get(); 57 | int S = val &0xF; 58 | int V = val >>4 &0xF; 59 | 60 | int fg = Color.HSVToColor(new float[]{H/255f * 360f, S/31f + 0.5f, V/31f + 0.5f}); 61 | int bg = 0; 62 | 63 | int colors[] = new int[25]; 64 | 65 | for (int i=0; i<3; i++){ 66 | val = ((i&1)==0) ? b.get() : (byte) (val >> 4); 67 | // use 4 bits to pick a 5 bit pattern so we never get all on or all off 68 | int bitPattern = patterns[val & 0xf]; 69 | colors[i] = ((bitPattern & 1) !=0) ? fg : bg; 70 | colors[i+5] = ((bitPattern & 2) !=0) ? fg : bg; 71 | colors[i+10] = ((bitPattern & 4) !=0) ? fg : bg; 72 | colors[i+15] = ((bitPattern & 8) !=0) ? fg : bg; 73 | colors[i+20] = ((bitPattern & 16) !=0) ? fg : bg; 74 | } 75 | // copy column 1 to 5 and 2 to 4 76 | for (int i=0;i<25;i+=5){ 77 | colors[i+4] = colors[i]; 78 | colors[i+3] = colors[i+1]; 79 | } 80 | 81 | bitPattern = Bitmap.createBitmap(colors, 5, 5, Bitmap.Config.ARGB_8888); 82 | bitmapPaint = new Paint(); 83 | bitmapPaint.setAntiAlias(false); 84 | } 85 | 86 | @Override 87 | public void draw(@NonNull Canvas canvas) { 88 | dest.set(0,0,canvas.getWidth(),canvas.getHeight()); 89 | dest.inset(canvas.getWidth() / 10,canvas.getHeight() / 10); 90 | canvas.drawColor(0xFFF0F0F0); 91 | canvas.drawBitmap(bitPattern, null, dest, bitmapPaint); 92 | } 93 | 94 | @Override 95 | public void setAlpha(@IntRange(from = 0, to = 255) int i) { 96 | 97 | } 98 | 99 | @Override 100 | public void setColorFilter(@Nullable ColorFilter colorFilter) { 101 | 102 | } 103 | 104 | @Override 105 | public int getOpacity() { 106 | return PixelFormat.UNKNOWN; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/views/MessageViewHolder.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.views; 2 | 3 | import android.view.ViewGroup; 4 | 5 | import org.servalproject.servalchat.R; 6 | 7 | /** 8 | * Created by jeremy on 10/01/18. 9 | */ 10 | 11 | public class MessageViewHolder extends BasicViewHolder { 12 | public MessageViewHolder(int textResource, ViewGroup parent) { 13 | super(R.layout.error, parent); 14 | DisplayError e = (DisplayError)itemView.findViewById(R.id.error); 15 | e.setText(textResource); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/views/ObservedRecyclerView.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.views; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.util.AttributeSet; 8 | 9 | import org.servalproject.mid.Identity; 10 | import org.servalproject.mid.ListObserver; 11 | import org.servalproject.mid.ListObserverSet; 12 | import org.servalproject.mid.Peer; 13 | import org.servalproject.servalchat.navigation.ILifecycle; 14 | import org.servalproject.servalchat.navigation.INavigate; 15 | import org.servalproject.servalchat.navigation.MainActivity; 16 | import org.servalproject.servalchat.navigation.Navigation; 17 | 18 | /** 19 | * Created by jeremy on 20/06/16. 20 | */ 21 | public abstract class ObservedRecyclerView 22 | extends SimpleRecyclerView 23 | implements ILifecycle, INavigate, ListObserver { 24 | 25 | protected MainActivity activity; 26 | protected Identity identity; 27 | protected Peer peer; 28 | private ListObserverSet observerSet; 29 | private int generation = -1; 30 | 31 | @Override 32 | public void added(T obj) { 33 | notifyChanged(); 34 | } 35 | 36 | @Override 37 | public void removed(T obj) { 38 | notifyChanged(); 39 | } 40 | 41 | @Override 42 | public void updated(T obj) { 43 | notifyChanged(); 44 | } 45 | 46 | @Override 47 | public void reset() { 48 | notifyChanged(); 49 | } 50 | 51 | public ObservedRecyclerView(ListObserverSet observerSet, Context context, @Nullable AttributeSet attrs, int emptyResource) { 52 | super(context, attrs, emptyResource); 53 | this.observerSet = observerSet; 54 | } 55 | 56 | public void setObserverSet(ListObserverSet observerSet) { 57 | this.observerSet = observerSet; 58 | } 59 | 60 | @Override 61 | public void onVisible() { 62 | if (observerSet != null) { 63 | int g = observerSet.add(this); 64 | if (g != generation) 65 | notifyChanged(); 66 | } 67 | } 68 | 69 | @Override 70 | public void onHidden() { 71 | if (observerSet != null) 72 | generation = observerSet.remove(this); 73 | } 74 | 75 | @Override 76 | public void onDetach(boolean configChanging) { 77 | 78 | } 79 | 80 | @Override 81 | public ILifecycle onAttach(MainActivity activity, Navigation n, Identity id, Peer peer, Bundle args) { 82 | this.activity = activity; 83 | this.identity = id; 84 | this.peer = peer; 85 | return this; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/views/Presenter.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.views; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | 6 | import org.servalproject.mid.Identity; 7 | import org.servalproject.servalchat.navigation.ILifecycle; 8 | 9 | /** 10 | * Created by jeremy on 20/07/16. 11 | */ 12 | public abstract class Presenter implements ILifecycle { 13 | private V view; 14 | 15 | public final String key; 16 | public final Identity identity; 17 | private PresenterFactory factory; 18 | 19 | protected Presenter(PresenterFactory factory, String key, Identity identity) { 20 | this.key = key; 21 | this.identity = identity; 22 | this.factory = factory; 23 | } 24 | 25 | protected final V getView() { 26 | return view; 27 | } 28 | 29 | public final void takeView(V view) { 30 | this.view = view; 31 | if (view != null) 32 | bind(view); 33 | } 34 | 35 | protected void bind(V view) { 36 | } 37 | 38 | protected void save(Bundle config) { 39 | 40 | } 41 | 42 | protected void restore(Bundle config) { 43 | 44 | } 45 | 46 | protected void onDestroy() { 47 | 48 | } 49 | 50 | @Override 51 | public void onDetach(boolean changingConfig) { 52 | if (factory == null) 53 | return; 54 | takeView(null); 55 | if (!changingConfig) { 56 | factory.release(this); 57 | factory = null; 58 | onDestroy(); 59 | } 60 | } 61 | 62 | @Override 63 | public void onVisible() { 64 | 65 | } 66 | 67 | @Override 68 | public void onHidden() { 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/views/PresenterFactory.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.views; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | 6 | import org.servalproject.mid.Identity; 7 | import org.servalproject.mid.Peer; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | /** 13 | * Created by jeremy on 20/07/16. 14 | */ 15 | public abstract class PresenterFactory> { 16 | 17 | private Map presenters = new HashMap<>(); 18 | 19 | protected String getKey(V view, Identity id, Peer peer, Bundle savedState) { 20 | if (id == null) 21 | return "null"; 22 | if (peer == null) 23 | return id.subscriber.signingKey.toHex(); 24 | return id.subscriber.signingKey.toHex() + peer.getSubscriber().sid.toHex(); 25 | } 26 | 27 | public final P getPresenter(V view, Identity id, Peer peer, Bundle savedState) { 28 | String key = getKey(view, id, peer, savedState); 29 | P ret = presenters.get(key); 30 | if (ret == null) { 31 | ret = create(key, id, peer); 32 | ret.restore(savedState); 33 | } 34 | ret.takeView(view); 35 | presenters.put(key, ret); 36 | return ret; 37 | } 38 | 39 | public void release(Presenter presenter) { 40 | presenters.remove(presenter.key); 41 | } 42 | 43 | protected abstract P create(String key, Identity id, Peer peer); 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/views/RecyclerHelper.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.views; 2 | 3 | import android.support.v7.widget.DividerItemDecoration; 4 | import android.support.v7.widget.LinearLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | 7 | /** 8 | * Created by jeremy on 14/11/16. 9 | */ 10 | 11 | public class RecyclerHelper { 12 | private RecyclerHelper(){} 13 | 14 | public static void createLayoutManager(RecyclerView view, boolean vertical, boolean reverse){ 15 | LinearLayoutManager layoutManager = new LinearLayoutManager( 16 | view.getContext(), 17 | vertical?LinearLayoutManager.VERTICAL:LinearLayoutManager.HORIZONTAL, 18 | reverse); 19 | view.setLayoutManager(layoutManager); 20 | } 21 | 22 | public static void createDivider(RecyclerView view){ 23 | LinearLayoutManager layoutManager = (LinearLayoutManager)view.getLayoutManager(); 24 | view.addItemDecoration(new DividerItemDecoration(view.getContext(), layoutManager.getOrientation())); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/views/SimpleRecyclerView.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.views; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.widget.DividerItemDecoration; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.util.AttributeSet; 9 | import android.view.ViewGroup; 10 | 11 | /** 12 | * Created by jeremy on 11/07/16. 13 | */ 14 | public abstract class SimpleRecyclerView 15 | extends RecyclerView { 16 | protected final ListAdapter listAdapter; 17 | private final int emptyResource; 18 | 19 | public SimpleRecyclerView(Context context, @Nullable AttributeSet attrs) { 20 | this(context, attrs, -1); 21 | } 22 | 23 | public SimpleRecyclerView(Context context, @Nullable AttributeSet attrs, int emptyResource) { 24 | super(context, attrs); 25 | this.emptyResource = emptyResource; 26 | listAdapter = new ListAdapter(); 27 | } 28 | 29 | @Override 30 | protected void onAttachedToWindow() { 31 | setAdapter(listAdapter); 32 | super.onAttachedToWindow(); 33 | } 34 | 35 | protected int getItemType(T item) { 36 | return 0; 37 | } 38 | 39 | abstract protected H createHolder(ViewGroup parent, int viewType); 40 | 41 | abstract protected void bind(H holder, T item); 42 | 43 | protected void unBind(H holder, T item) { 44 | } 45 | 46 | protected long getId(T item) { 47 | return -1; 48 | } 49 | 50 | abstract protected T get(int position); 51 | 52 | abstract protected int getCount(); 53 | 54 | protected void notifyChanged() { 55 | listAdapter.notifyDataSetChanged(); 56 | } 57 | 58 | public class ListAdapter extends RecyclerView.Adapter { 59 | @Override 60 | public BasicViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 61 | if (viewType == 0) 62 | return new MessageViewHolder(emptyResource, parent); 63 | return createHolder(parent, viewType -1); 64 | } 65 | 66 | @Override 67 | public void onViewRecycled(BasicViewHolder holder) { 68 | if (holder instanceof MessageViewHolder) 69 | return; 70 | int position = holder.getAdapterPosition(); 71 | if (position != NO_POSITION) 72 | //noinspection unchecked 73 | unBind((H)holder, get(position)); 74 | } 75 | 76 | @Override 77 | public void onBindViewHolder(BasicViewHolder holder, int position) { 78 | if (holder instanceof MessageViewHolder) 79 | return; 80 | //noinspection unchecked 81 | bind((H)holder, get(position)); 82 | } 83 | 84 | @Override 85 | public int getItemCount() { 86 | int count = getCount(); 87 | if (count == 0 && emptyResource!=-1) 88 | return 1; 89 | return getCount(); 90 | } 91 | 92 | public long getItemId(int position) { 93 | if (getCount()==0) 94 | return -1; 95 | return getId(get(position)); 96 | } 97 | 98 | @Override 99 | public int getItemViewType(int position) { 100 | if (getCount()==0) 101 | return 0; 102 | return getItemType(get(position))+1; 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/views/SpinnerViewHolder.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.views; 2 | 3 | import android.view.ViewGroup; 4 | 5 | import org.servalproject.servalchat.R; 6 | 7 | /** 8 | * Created by jeremy on 30/01/17. 9 | */ 10 | 11 | public class SpinnerViewHolder extends BasicViewHolder { 12 | public SpinnerViewHolder(ViewGroup parent) { 13 | super(R.layout.progress, parent); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/views/TimestampView.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.views; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import java.text.DateFormat; 9 | import java.util.Date; 10 | 11 | /** 12 | * Created by jeremy on 29/03/17. 13 | */ 14 | 15 | public class TimestampView extends TextView { 16 | public TimestampView(Context context) { 17 | super(context); 18 | } 19 | 20 | public TimestampView(Context context, AttributeSet attrs) { 21 | super(context, attrs); 22 | } 23 | 24 | public TimestampView(Context context, AttributeSet attrs, int defStyleAttr) { 25 | super(context, attrs, defStyleAttr); 26 | } 27 | 28 | private static DateFormat df = DateFormat.getDateInstance(); 29 | private static DateFormat tf = DateFormat.getTimeInstance(DateFormat.SHORT); 30 | 31 | public void setDates(Date date, Date previous){ 32 | if (date.getTime()<=0){ 33 | setVisibility(View.GONE); 34 | } else { 35 | String thisDate = df.format(date); 36 | if (!thisDate.equals(df.format(previous==null ? new Date() : previous))) { 37 | setText(thisDate+" "+tf.format(date)); 38 | setVisibility(View.VISIBLE); 39 | } else if (previous==null || previous.getTime() - date.getTime() >= 30 * 60 * 1000) { 40 | setText(tf.format(date)); 41 | setVisibility(View.VISIBLE); 42 | } else { 43 | setVisibility(View.GONE); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/org/servalproject/servalchat/views/UIObserver.java: -------------------------------------------------------------------------------- 1 | package org.servalproject.servalchat.views; 2 | 3 | import android.util.Log; 4 | 5 | import org.servalproject.mid.Observer; 6 | import org.servalproject.mid.IObserverSet; 7 | import org.servalproject.servalchat.navigation.ILifecycle; 8 | 9 | public abstract class UIObserver implements ILifecycle, Observer { 10 | private final IObserverSet source; 11 | 12 | protected UIObserver(IObserverSet source){ 13 | this.source = source; 14 | } 15 | 16 | @Override 17 | public abstract void updated(T obj); 18 | 19 | @Override 20 | public void onDetach(boolean configChange) { 21 | 22 | } 23 | 24 | @Override 25 | public void onVisible() { 26 | source.addUI(this); 27 | updated(source.getObj()); 28 | } 29 | 30 | @Override 31 | public void onHidden() { 32 | source.removeUI(this); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/jni/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH:= $(call my-dir) 2 | 3 | SODIUM_ARCH_FOLDER := $(APP_ABI) 4 | ifeq ($(SODIUM_ARCH_FOLDER),armeabi-v7a) 5 | SODIUM_ARCH_FOLDER = armv7-a 6 | endif 7 | ifeq ($(SODIUM_ARCH_FOLDER),arm64-v8a) 8 | SODIUM_ARCH_FOLDER = armv8-a 9 | endif 10 | ifeq ($(SODIUM_ARCH_FOLDER),x86) 11 | SODIUM_ARCH_FOLDER = i686 12 | endif 13 | ifeq ($(SODIUM_ARCH_FOLDER),x86_64) 14 | SODIUM_ARCH_FOLDER = westmere 15 | endif 16 | 17 | SODIUM_BASE := libsodium/libsodium-android-$(SODIUM_ARCH_FOLDER) 18 | SODIUM_INCLUDE := $(LOCAL_PATH)/$(SODIUM_BASE)/include 19 | 20 | include $(CLEAR_VARS) 21 | LOCAL_MODULE:= sodium 22 | LOCAL_SRC_FILES:= $(SODIUM_BASE)/lib/libsodium.a 23 | include $(PREBUILT_STATIC_LIBRARY) 24 | 25 | include $(CLEAR_VARS) 26 | LOCAL_STATIC_LIBRARIES := servaldstatic 27 | LOCAL_C_INCLUDES += $(LOCAL_PATH)/serval-dna 28 | LOCAL_SRC_FILES := $(LOCAL_PATH)/chat_features.c 29 | LOCAL_MODULE := servaldaemon 30 | LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog 31 | include $(BUILD_SHARED_LIBRARY) 32 | 33 | # Build serval library 34 | include $(CLEAR_VARS) 35 | include $(LOCAL_PATH)/serval-dna/Android.mk 36 | -------------------------------------------------------------------------------- /app/src/main/jni/Application.mk: -------------------------------------------------------------------------------- 1 | APP_PLATFORM := android-16 2 | APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 3 | -------------------------------------------------------------------------------- /app/src/main/jni/chat_features.c: -------------------------------------------------------------------------------- 1 | /* 2 | Serval DNA daemon features 3 | Copyright (C) 2016 Flinders University 4 | 5 | This program is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU General Public License 7 | as published by the Free Software Foundation; either version 2 8 | of the License, or (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | 20 | #include "feature.h" 21 | 22 | void servald_features() 23 | { 24 | USE_FEATURE(cli_config); 25 | 26 | USE_FEATURE(mdp_binding_MDP_PORT_ECHO); 27 | USE_FEATURE(mdp_binding_MDP_PORT_TRACE); 28 | USE_FEATURE(mdp_binding_MDP_PORT_KEYMAPREQUEST); 29 | USE_FEATURE(mdp_binding_MDP_PORT_DNALOOKUP); 30 | USE_FEATURE(mdp_binding_MDP_PORT_PROBE); 31 | USE_FEATURE(mdp_binding_MDP_PORT_STUN); 32 | USE_FEATURE(mdp_binding_MDP_PORT_STUNREQ); 33 | USE_FEATURE(mdp_binding_MDP_PORT_LINKSTATE); 34 | USE_FEATURE(mdp_binding_MDP_PORT_RHIZOME_SYNC); 35 | USE_FEATURE(mdp_binding_MDP_PORT_RHIZOME_SYNC_KEYS); 36 | USE_FEATURE(mdp_binding_MDP_PORT_RHIZOME_REQUEST); 37 | USE_FEATURE(mdp_binding_MDP_PORT_RHIZOME_RESPONSE); 38 | USE_FEATURE(mdp_binding_MDP_PORT_RHIZOME_MANIFEST_REQUEST); 39 | 40 | USE_FEATURE(http_server); 41 | USE_FEATURE(http_rhizome); 42 | USE_FEATURE(http_rest_keyring); 43 | USE_FEATURE(http_rest_rhizome); 44 | USE_FEATURE(http_rest_meshms); 45 | USE_FEATURE(http_rest_meshmb); 46 | 47 | USE_FEATURE(log_output_file); 48 | USE_FEATURE(log_output_android); 49 | 50 | USE_FEATURE(jni_commandline); 51 | USE_FEATURE(jni_server); 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_account.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_contact.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_block_contact.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_contacts.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_remove_contact.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/ack_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 21 | 22 | 26 | 27 | 34 | 35 | 36 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/block_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/contacts.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/conversation_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/conversation_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/error.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/feed.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/feed_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/feed_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/identity.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/identity_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 18 | 19 | 26 | 27 | 31 | 32 | 38 | 39 | 45 | 46 |