├── .gitignore
├── .gitmodules
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── layer
│ │ │ └── messenger
│ │ │ ├── App.java
│ │ │ ├── AppSettingsActivity.java
│ │ │ ├── BaseActivity.java
│ │ │ ├── ConversationSettingsActivity.java
│ │ │ ├── ConversationsListActivity.java
│ │ │ ├── MessagesListActivity.java
│ │ │ ├── PushNotificationReceiver.java
│ │ │ ├── ResumeActivity.java
│ │ │ ├── makemoji
│ │ │ ├── MakeMojiAtlasComposer.java
│ │ │ ├── MakeMojiCellFactory.java
│ │ │ ├── MakeMojiConversationsAdapter.java
│ │ │ └── MakeMojiConversationsRecyclerView.java
│ │ │ └── util
│ │ │ ├── AuthenticationProvider.java
│ │ │ ├── Log.java
│ │ │ └── Util.java
│ └── res
│ │ ├── drawable-hdpi
│ │ ├── ic_block_white_24dp.png
│ │ └── notification.png
│ │ ├── drawable-ldpi
│ │ └── notification.png
│ │ ├── drawable-mdpi
│ │ ├── ic_block_white_24dp.png
│ │ └── notification.png
│ │ ├── drawable-nodpi
│ │ └── layer_launcher.png
│ │ ├── drawable-xhdpi
│ │ ├── ic_block_white_24dp.png
│ │ └── notification.png
│ │ ├── drawable-xxhdpi
│ │ ├── ic_block_white_24dp.png
│ │ └── notification.png
│ │ ├── drawable-xxxhdpi
│ │ ├── ic_block_white_24dp.png
│ │ └── notification.png
│ │ ├── layout
│ │ ├── activity_app_settings.xml
│ │ ├── activity_conversation_settings.xml
│ │ ├── activity_conversations_list.xml
│ │ ├── activity_messages_list.xml
│ │ ├── activity_resume.xml
│ │ ├── atlas_message_composer.xml
│ │ └── participant_item.xml
│ │ ├── menu
│ │ ├── menu_conversation_details.xml
│ │ ├── menu_conversations_list.xml
│ │ ├── menu_messages_list.xml
│ │ └── menu_settings.xml
│ │ ├── values-v21
│ │ └── styles.xml
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── strings.xml
│ │ └── styles.xml
│ ├── providerdemo
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── layer
│ │ │ └── messenger
│ │ │ └── flavor
│ │ │ ├── AppIdScanner.java
│ │ │ ├── DemoAtlasIdScannerActivity.java
│ │ │ ├── DemoAuthenticationProvider.java
│ │ │ ├── DemoLoginActivity.java
│ │ │ ├── DemoParticipant.java
│ │ │ ├── DemoParticipantProvider.java
│ │ │ └── Flavor.java
│ └── res
│ │ ├── drawable
│ │ └── app_id_scanner_outline.xml
│ │ ├── layout
│ │ ├── activity_app_id_scanner.xml
│ │ └── activity_login_demo.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ └── strings.xml
│ └── providerrails
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── layer
│ │ └── messenger
│ │ └── flavor
│ │ ├── Flavor.java
│ │ ├── RailsAuthenticationProvider.java
│ │ ├── RailsLoginActivity.java
│ │ ├── RailsParticipant.java
│ │ ├── RailsParticipantProvider.java
│ │ └── util
│ │ └── CustomEndpoint.java
│ └── res
│ ├── layout
│ └── activity_login_rails.xml
│ └── values
│ └── strings.xml
├── 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/
4 | .DS_Store
5 | build
6 | captures
7 | *.iml
8 | app/build
9 | *.apk
10 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/.gitmodules
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MakeMoji Atlas Messenger Example
2 |
3 | An example of how to use the the Atlas Messaging Framework together with the MakeMoji Android SDK.
4 | Refer to the original README below for Atlas instructions and [here](https://github.com/makemoji/MakemojiSDK-Android) for the MakeMoji SDK.
5 | To run the sample, add the line "mm.key=Your-key" to local.properties or directly into the projects build.gradle so it can be loaded in App.java.
6 |
7 |
8 | All new classes have been added under [com.layer.messenger.makemoji](app/src/main/java/com/layer/messenger/makemoji/).
9 | Use MakeMojiAtlasComposer and MakeMojiCellFactory and to compose and display MakeMoji messages.
10 | Copy the new version of [atlas_message_composer.xml](app/src/main/res/layout/atlas_message_composer.xml) to your project so the composer will use a compatible MojiEditText.
11 | Refer to the modified [MessagesListActivity](app/src/main/java/com/layer/messenger/MessagesListActivity.java) to see how to use the Atlas composer and MojiInputLayout together.
12 |
13 | # Atlas Messenger for Android
14 |
15 | Atlas Messenger is a fully-featured messaging app following [Material Design guidelines](https://www.google.com/design/spec/material-design/introduction.html#introduction-goals), built on top of the [Layer SDK](https://layer.com/), using the [Atlas UI toolkit](https://github.com/layerhq/Atlas-Android).
16 |
17 | ##Structure
18 |
19 | * **App:** Application class.
20 | * Activities:
21 | * **BaseActivity:** Base Activity class for handling menu titles and the menu back button and ensuring the `LayerClient` is connected when resuming Activities.
22 | * **ConversationsListActivity:** List of all Conversations for the authenticated user.
23 | * **MessagesListActivity:** List of Messages within a particular Conversation. Also handles message composition and addressing.
24 | * **SettingsActivity:** Global application settings.
25 | * **ConversationDetailsActivity:** Settings for a particular Conversation.
26 | * **PushNotificationReceiver:** Handles `com.layer.sdk.PUSH` Intents and displays notifications.
27 | * **AuthenticationProvider:** Interface used by the Messenger app to authenticate users. Default implementations are provided by gradle `flavors`; see *Build Variants* below.
28 |
29 | ##Identity Providers
30 |
31 | Atlas Messenger uses the `AuthenticationProvider` interface to authenticate with various backends. Additional identity providers can integrate with Atlas Messenger by implementing `AuthenticationProvider` and using a custom login Activity, similar to the provided flavors below.
32 |
33 | ###Provided Flavors
34 | Two default implementations are provided via [product flavors](http://developer.android.com/tools/building/configuring-gradle.html#workBuildVariants), where each flavor implements a custom `AuthenticationProvider`, a custom Atlas `Participant`, and provides login Activities for gathering their required credentials:
35 |
36 | 1. **atlasprovider:** For use in the [Layer Atlas demo](https://getatlas.layer.com/android). This authentication flow utilizes a QR-Code scanner to capture a Layer App ID from the Layer developer dashboard. The scanner can be bypassed by supplying your Atlas demo App ID in the `App.LAYER_APP_ID` constant.
37 | 2. **herokuprovider:** For use with the deployable [Rails Provider](https://github.com/layerhq/layer-identity-provider) backend. Your Layer App ID must be set in the `App.LAYER_APP_ID` constant.
38 |
39 | In Android Studio, switch flavors using Build Variants, typically in the side tab on the lower left of the Android Studio window.
40 |
41 | ##Contributing
42 | Atlas is an Open Source project maintained by Layer. Feedback and contributions are always welcome and the maintainers try to process patches as quickly as possible. Feel free to open up a Pull Request or Issue on Github.
43 |
44 | ##License
45 |
46 | Atlas is licensed under the terms of the [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). Please see the [LICENSE](LICENSE) file for full details.
47 |
48 | ##Contact
49 |
50 | Atlas was developed in San Francisco by the Layer team. If you have any technical questions or concerns about this project feel free to reach out to [Layer Support](mailto:support@layer.com).
51 |
52 | ###Credits
53 |
54 | * [Steven Jones](https://github.com/sjones94549)
55 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | src/providerrails/res/raw/
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.2"
6 |
7 | defaultConfig {
8 | applicationId "com.layer.messenger"
9 | minSdkVersion 15
10 | targetSdkVersion 23
11 | versionCode 30
12 | versionName "$versionCode"
13 | buildConfigField "String", "MakeMojiKey", "\""+getProps('mm.key')+"\""
14 | }
15 |
16 | productFlavors {
17 | providerrails {
18 | applicationId = defaultConfig.applicationId + ".providerrails"
19 | }
20 |
21 | providerdemo {
22 | applicationId = defaultConfig.applicationId + ".providerdemo"
23 | }
24 | }
25 |
26 | buildTypes {
27 | release {
28 | minifyEnabled false
29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
30 | }
31 | }
32 | }
33 |
34 | dependencies {
35 | // All flavors
36 | compile 'com.layer.atlas:layer-atlas:0.2.11'
37 |
38 | // Flavor-specific dependencies
39 | providerdemoCompile 'com.google.android.gms:play-services-vision:8.4.0'
40 | compile 'com.makemoji:makemoji-sdk-android:0.9.725'
41 | }
42 | repositories {
43 | jcenter()
44 | maven {
45 | url "https://dl.bintray.com/mm/maven/"
46 | }
47 | }
48 | def getProps(String propName) {
49 | def propsFile = rootProject.file('local.properties')
50 | if (propsFile.exists()) {
51 | def props = new Properties()
52 | props.load(new FileInputStream(propsFile))
53 | return props[propName]
54 | } else {
55 | return "";
56 | }
57 | }
--------------------------------------------------------------------------------
/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 /Users/steven/dev/android-sdk-macosx/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/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
28 |
29 |
30 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
52 |
53 |
54 |
55 |
58 |
59 |
60 |
61 |
64 |
65 |
66 |
67 |
70 |
71 |
72 |
73 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/app/src/main/java/com/layer/messenger/App.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.content.Context;
6 |
7 | import com.layer.atlas.messagetypes.text.TextCellFactory;
8 | import com.layer.atlas.messagetypes.threepartimage.ThreePartImageUtils;
9 | import com.layer.atlas.provider.ParticipantProvider;
10 | import com.layer.atlas.util.Util;
11 | import com.layer.atlas.util.picasso.requesthandlers.MessagePartRequestHandler;
12 | import com.layer.messenger.util.AuthenticationProvider;
13 | import com.layer.sdk.LayerClient;
14 | import com.makemoji.mojilib.Moji;
15 | import com.squareup.picasso.Picasso;
16 |
17 | import java.util.Arrays;
18 |
19 | /**
20 | * App provides static access to a LayerClient and other Atlas and Messenger context, including
21 | * AuthenticationProvider, ParticipantProvider, Participant, and Picasso.
22 | *
23 | * App.Flavor allows build variants to target different environments, such as the Atlas Demo and the
24 | * open source Rails Identity Provider. Switch flavors with the Android Studio `Build Variant` tab.
25 | * When using a flavor besides the Atlas Demo you must manually set your Layer App ID and GCM Sender
26 | * ID in that flavor's Flavor.java.
27 | *
28 | * @see com.layer.messenger.App.Flavor
29 | * @see com.layer.messenger.flavor.Flavor
30 | * @see LayerClient
31 | * @see ParticipantProvider
32 | * @see Picasso
33 | * @see AuthenticationProvider
34 | */
35 | public class App extends Application {
36 |
37 | private static Application sInstance;
38 | private static Flavor sFlavor = new com.layer.messenger.flavor.Flavor();
39 |
40 | private static LayerClient sLayerClient;
41 | private static ParticipantProvider sParticipantProvider;
42 | private static AuthenticationProvider sAuthProvider;
43 | private static Picasso sPicasso;
44 |
45 |
46 | //==============================================================================================
47 | // Application Overrides
48 | //==============================================================================================
49 |
50 | @Override
51 | public void onCreate() {
52 | super.onCreate();
53 | Moji.initialize(this,BuildConfig.MakeMojiKey);
54 |
55 | // Enable verbose logging in debug builds
56 | if (BuildConfig.DEBUG) {
57 | com.layer.atlas.util.Log.setLoggingEnabled(true);
58 | com.layer.messenger.util.Log.setAlwaysLoggable(true);
59 | LayerClient.setLoggingEnabled(this, true);
60 | }
61 |
62 | // Allow the LayerClient to track app state
63 | LayerClient.applicationCreated(this);
64 |
65 | sInstance = this;
66 | }
67 |
68 | public static Application getInstance() {
69 | return sInstance;
70 | }
71 |
72 |
73 | //==============================================================================================
74 | // Identity Provider Methods
75 | //==============================================================================================
76 |
77 | /**
78 | * Routes the user to the proper Activity depending on their authenticated state. Returns
79 | * `true` if the user has been routed to another Activity, or `false` otherwise.
80 | *
81 | * @param from Activity to route from.
82 | * @return `true` if the user has been routed to another Activity, or `false` otherwise.
83 | */
84 | public static boolean routeLogin(Activity from) {
85 | return getAuthenticationProvider().routeLogin(getLayerClient(), getLayerAppId(), from);
86 | }
87 |
88 | /**
89 | * Authenticates with the AuthenticationProvider and Layer, returning asynchronous results to
90 | * the provided callback.
91 | *
92 | * @param credentials Credentials associated with the current AuthenticationProvider.
93 | * @param callback Callback to receive authentication results.
94 | */
95 | @SuppressWarnings("unchecked")
96 | public static void authenticate(Object credentials, AuthenticationProvider.Callback callback) {
97 | LayerClient client = getLayerClient();
98 | if (client == null) return;
99 | String layerAppId = getLayerAppId();
100 | if (layerAppId == null) return;
101 | getAuthenticationProvider()
102 | .setCredentials(credentials)
103 | .setCallback(callback);
104 | client.authenticate();
105 | }
106 |
107 | /**
108 | * Deauthenticates with Layer and clears cached AuthenticationProvider credentials.
109 | *
110 | * @param callback Callback to receive deauthentication success and failure.
111 | */
112 | public static void deauthenticate(final Util.DeauthenticationCallback callback) {
113 | Util.deauthenticate(getLayerClient(), new Util.DeauthenticationCallback() {
114 | @Override
115 | @SuppressWarnings("unchecked")
116 | public void onDeauthenticationSuccess(LayerClient client) {
117 | getAuthenticationProvider().setCredentials(null);
118 | callback.onDeauthenticationSuccess(client);
119 | }
120 |
121 | @Override
122 | public void onDeauthenticationFailed(LayerClient client, String reason) {
123 | callback.onDeauthenticationFailed(client, reason);
124 | }
125 | });
126 | }
127 |
128 |
129 | //==============================================================================================
130 | // Getters / Setters
131 | //==============================================================================================
132 |
133 | /**
134 | * Gets or creates a LayerClient, using a default set of LayerClient.Options and flavor-specific
135 | * App ID and Options from the `generateLayerClient` method. Returns `null` if the flavor was
136 | * unable to create a LayerClient (due to no App ID, etc.).
137 | *
138 | * @return New or existing LayerClient, or `null` if a LayerClient could not be constructed.
139 | * @see Flavor#generateLayerClient(Context, LayerClient.Options)
140 | */
141 | public static LayerClient getLayerClient() {
142 | if (sLayerClient == null) {
143 | // Custom options for constructing a LayerClient
144 | LayerClient.Options options = new LayerClient.Options()
145 |
146 | /* Fetch the minimum amount per conversation when first authenticated */
147 | .historicSyncPolicy(LayerClient.Options.HistoricSyncPolicy.FROM_LAST_MESSAGE)
148 |
149 | /* Automatically download text and ThreePartImage info/preview */
150 | .autoDownloadMimeTypes(Arrays.asList(
151 | TextCellFactory.MIME_TYPE,
152 | ThreePartImageUtils.MIME_TYPE_INFO,
153 | ThreePartImageUtils.MIME_TYPE_PREVIEW));
154 |
155 | // Allow flavor to specify Layer App ID and customize Options.
156 | sLayerClient = sFlavor.generateLayerClient(sInstance, options);
157 |
158 | // Flavor was unable to generate Layer Client (no App ID, etc.)
159 | if (sLayerClient == null) return null;
160 |
161 | /* Register AuthenticationProvider for handling authentication challenges */
162 | sLayerClient.registerAuthenticationListener(getAuthenticationProvider());
163 | }
164 | return sLayerClient;
165 | }
166 |
167 | public static String getLayerAppId() {
168 | return sFlavor.getLayerAppId();
169 | }
170 |
171 | public static ParticipantProvider getParticipantProvider() {
172 | if (sParticipantProvider == null) {
173 | sParticipantProvider = sFlavor.generateParticipantProvider(sInstance, getAuthenticationProvider());
174 | }
175 | return sParticipantProvider;
176 | }
177 |
178 | public static AuthenticationProvider getAuthenticationProvider() {
179 | if (sAuthProvider == null) {
180 | sAuthProvider = sFlavor.generateAuthenticationProvider(sInstance);
181 |
182 | // If we have cached credentials, try authenticating with Layer
183 | LayerClient layerClient = getLayerClient();
184 | if (layerClient != null && sAuthProvider.hasCredentials()) layerClient.authenticate();
185 | }
186 | return sAuthProvider;
187 | }
188 |
189 | public static Picasso getPicasso() {
190 | if (sPicasso == null) {
191 | // Picasso with custom RequestHandler for loading from Layer MessageParts.
192 | sPicasso = new Picasso.Builder(sInstance)
193 | .addRequestHandler(new MessagePartRequestHandler(getLayerClient()))
194 | .build();
195 | }
196 | return sPicasso;
197 | }
198 |
199 | /**
200 | * Flavor is used by Atlas Messenger to switch environments.
201 | *
202 | * @see com.layer.messenger.flavor.Flavor
203 | */
204 | public interface Flavor {
205 | String getLayerAppId();
206 |
207 | LayerClient generateLayerClient(Context context, LayerClient.Options options);
208 |
209 | AuthenticationProvider generateAuthenticationProvider(Context context);
210 |
211 | ParticipantProvider generateParticipantProvider(Context context, AuthenticationProvider authenticationProvider);
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/app/src/main/java/com/layer/messenger/AppSettingsActivity.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.ProgressDialog;
5 | import android.content.DialogInterface;
6 | import android.os.Build;
7 | import android.os.Bundle;
8 | import android.text.TextUtils;
9 | import android.view.View;
10 | import android.widget.Button;
11 | import android.widget.CompoundButton;
12 | import android.widget.Switch;
13 | import android.widget.TextView;
14 | import android.widget.Toast;
15 |
16 | import com.layer.atlas.AtlasAvatar;
17 | import com.layer.atlas.provider.Participant;
18 | import com.layer.atlas.util.Util;
19 | import com.layer.messenger.util.Log;
20 | import com.layer.sdk.LayerClient;
21 | import com.layer.sdk.changes.LayerChangeEvent;
22 | import com.layer.sdk.exceptions.LayerException;
23 | import com.layer.sdk.listeners.LayerAuthenticationListener;
24 | import com.layer.sdk.listeners.LayerChangeEventListener;
25 | import com.layer.sdk.listeners.LayerConnectionListener;
26 | import com.layer.sdk.messaging.Conversation;
27 |
28 | import java.util.List;
29 |
30 | public class AppSettingsActivity extends BaseActivity implements LayerConnectionListener, LayerAuthenticationListener, LayerChangeEventListener, View.OnLongClickListener {
31 | /* Account */
32 | private AtlasAvatar mAvatar;
33 | private TextView mUserName;
34 | private TextView mUserState;
35 | private Button mLogoutButton;
36 |
37 | /* Notifications */
38 | private Switch mShowNotifications;
39 |
40 | /* Debug */
41 | private Switch mVerboseLogging;
42 | private TextView mAppVersion;
43 | private TextView mAndroidVersion;
44 | private TextView mAtlasVersion;
45 | private TextView mLayerVersion;
46 | private TextView mUserId;
47 |
48 | /* Statistics */
49 | private TextView mConversationCount;
50 | private TextView mMessageCount;
51 | private TextView mUnreadMessageCount;
52 |
53 | /* Rich Content */
54 | private TextView mDiskUtilization;
55 | private TextView mDiskAllowance;
56 | private TextView mAutoDownloadMimeTypes;
57 |
58 | public AppSettingsActivity() {
59 | super(R.layout.activity_app_settings, R.menu.menu_settings, R.string.title_settings, true);
60 | }
61 |
62 | @Override
63 | protected void onCreate(Bundle savedInstanceState) {
64 | super.onCreate(savedInstanceState);
65 |
66 | // View cache
67 | mAvatar = (AtlasAvatar) findViewById(R.id.avatar);
68 | mUserName = (TextView) findViewById(R.id.user_name);
69 | mUserState = (TextView) findViewById(R.id.user_state);
70 | mLogoutButton = (Button) findViewById(R.id.logout_button);
71 | mShowNotifications = (Switch) findViewById(R.id.show_notifications_switch);
72 | mVerboseLogging = (Switch) findViewById(R.id.logging_switch);
73 | mAppVersion = (TextView) findViewById(R.id.app_version);
74 | mAtlasVersion = (TextView) findViewById(R.id.atlas_version);
75 | mLayerVersion = (TextView) findViewById(R.id.layer_version);
76 | mAndroidVersion = (TextView) findViewById(R.id.android_version);
77 | mUserId = (TextView) findViewById(R.id.user_id);
78 | mConversationCount = (TextView) findViewById(R.id.conversation_count);
79 | mMessageCount = (TextView) findViewById(R.id.message_count);
80 | mUnreadMessageCount = (TextView) findViewById(R.id.unread_message_count);
81 | mDiskUtilization = (TextView) findViewById(R.id.disk_utilization);
82 | mDiskAllowance = (TextView) findViewById(R.id.disk_allowance);
83 | mAutoDownloadMimeTypes = (TextView) findViewById(R.id.auto_download_mime_types);
84 | mAvatar.init(getParticipantProvider(), getPicasso());
85 |
86 | // Long-click copy-to-clipboard
87 | mUserName.setOnLongClickListener(this);
88 | mUserState.setOnLongClickListener(this);
89 | mAppVersion.setOnLongClickListener(this);
90 | mAndroidVersion.setOnLongClickListener(this);
91 | mAtlasVersion.setOnLongClickListener(this);
92 | mLayerVersion.setOnLongClickListener(this);
93 | mUserId.setOnLongClickListener(this);
94 | mConversationCount.setOnLongClickListener(this);
95 | mMessageCount.setOnLongClickListener(this);
96 | mUnreadMessageCount.setOnLongClickListener(this);
97 | mDiskUtilization.setOnLongClickListener(this);
98 | mDiskAllowance.setOnLongClickListener(this);
99 | mAutoDownloadMimeTypes.setOnLongClickListener(this);
100 |
101 | // Buttons and switches
102 | mLogoutButton.setOnClickListener(new View.OnClickListener() {
103 | @Override
104 | public void onClick(final View v) {
105 | setEnabled(false);
106 | new AlertDialog.Builder(AppSettingsActivity.this)
107 | .setCancelable(false)
108 | .setMessage(R.string.alert_message_logout)
109 | .setPositiveButton(R.string.alert_button_logout, new DialogInterface.OnClickListener() {
110 | @Override
111 | public void onClick(DialogInterface dialog, int which) {
112 | if (Log.isLoggable(Log.VERBOSE)) {
113 | Log.v("Deauthenticating");
114 | }
115 | dialog.dismiss();
116 | final ProgressDialog progressDialog = new ProgressDialog(AppSettingsActivity.this);
117 | progressDialog.setMessage(getResources().getString(R.string.alert_dialog_logout));
118 | progressDialog.setCancelable(false);
119 | progressDialog.show();
120 | App.deauthenticate(new Util.DeauthenticationCallback() {
121 | @Override
122 | public void onDeauthenticationSuccess(LayerClient client) {
123 | if (Log.isLoggable(Log.VERBOSE)) {
124 | Log.v("Successfully deauthenticated");
125 | }
126 | progressDialog.dismiss();
127 | setEnabled(true);
128 | App.routeLogin(AppSettingsActivity.this);
129 | }
130 |
131 | @Override
132 | public void onDeauthenticationFailed(LayerClient client, String reason) {
133 | if (Log.isLoggable(Log.ERROR)) {
134 | Log.e("Failed to deauthenticate: " + reason);
135 | }
136 | progressDialog.dismiss();
137 | setEnabled(true);
138 | Toast.makeText(AppSettingsActivity.this, getString(R.string.toast_failed_to_deauthenticate, reason), Toast.LENGTH_SHORT).show();
139 | }
140 | });
141 | }
142 | })
143 | .setNegativeButton(R.string.alert_button_cancel, new DialogInterface.OnClickListener() {
144 | @Override
145 | public void onClick(DialogInterface dialog, int which) {
146 | dialog.dismiss();
147 | setEnabled(true);
148 | }
149 | })
150 | .show();
151 | }
152 | });
153 |
154 | mShowNotifications.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
155 | @Override
156 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
157 | PushNotificationReceiver.getNotifications(AppSettingsActivity.this).setEnabled(isChecked);
158 | }
159 | });
160 |
161 | mVerboseLogging.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
162 | @Override
163 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
164 | LayerClient.setLoggingEnabled(AppSettingsActivity.this, isChecked);
165 | com.layer.atlas.util.Log.setLoggingEnabled(isChecked);
166 | Log.setAlwaysLoggable(isChecked);
167 | }
168 | });
169 | }
170 |
171 | @Override
172 | protected void onResume() {
173 | super.onResume();
174 | getLayerClient()
175 | .registerAuthenticationListener(this)
176 | .registerConnectionListener(this)
177 | .registerEventListener(this);
178 | refresh();
179 | }
180 |
181 | @Override
182 | protected void onPause() {
183 | getLayerClient()
184 | .unregisterAuthenticationListener(this)
185 | .unregisterConnectionListener(this)
186 | .unregisterEventListener(this);
187 | super.onPause();
188 | }
189 |
190 | public void setEnabled(final boolean enabled) {
191 | runOnUiThread(new Runnable() {
192 | @Override
193 | public void run() {
194 | mLogoutButton.setEnabled(enabled);
195 | mShowNotifications.setEnabled(enabled);
196 | mVerboseLogging.setEnabled(enabled);
197 | }
198 | });
199 | }
200 |
201 | private void refresh() {
202 | if (!getLayerClient().isAuthenticated()) return;
203 |
204 | /* Account */
205 | Participant participant = getParticipantProvider().getParticipant(getLayerClient().getAuthenticatedUserId());
206 | mAvatar.setParticipants(getLayerClient().getAuthenticatedUserId());
207 | mUserName.setText(participant.getName());
208 | mUserState.setText(getLayerClient().isConnected() ? R.string.settings_content_connected : R.string.settings_content_disconnected);
209 |
210 | /* Notifications */
211 | mShowNotifications.setChecked(PushNotificationReceiver.getNotifications(this).isEnabled());
212 |
213 | /* Debug */
214 | // enable logging through adb: `adb shell setprop log.tag.LayerSDK VERBOSE`
215 | boolean enabledByEnvironment = android.util.Log.isLoggable("LayerSDK", Log.VERBOSE);
216 | mVerboseLogging.setEnabled(!enabledByEnvironment);
217 | mVerboseLogging.setChecked(enabledByEnvironment || LayerClient.isLoggingEnabled());
218 | mAppVersion.setText(getString(R.string.settings_content_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
219 | mAtlasVersion.setText(Util.getVersion());
220 | mLayerVersion.setText(LayerClient.getVersion());
221 | mAndroidVersion.setText(getString(R.string.settings_content_android_version, Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
222 | mUserId.setText(getLayerClient().getAuthenticatedUserId());
223 |
224 | /* Statistics */
225 | long totalMessages = 0;
226 | long totalUnread = 0;
227 | List conversations = getLayerClient().getConversations();
228 | for (Conversation conversation : conversations) {
229 | totalMessages += conversation.getTotalMessageCount();
230 | totalUnread += conversation.getTotalUnreadMessageCount();
231 | }
232 | mConversationCount.setText(String.format("%d", conversations.size()));
233 | mMessageCount.setText(String.format("%d", totalMessages));
234 | mUnreadMessageCount.setText(String.format("%d", totalUnread));
235 |
236 | /* Rich Content */
237 | mDiskUtilization.setText(readableByteFormat(getLayerClient().getDiskUtilization()));
238 | long allowance = getLayerClient().getDiskCapacity();
239 | if (allowance == 0) {
240 | mDiskAllowance.setText(R.string.settings_content_disk_unlimited);
241 | } else {
242 | mDiskAllowance.setText(readableByteFormat(allowance));
243 | }
244 | mAutoDownloadMimeTypes.setText(TextUtils.join("\n", getLayerClient().getAutoDownloadMimeTypes()));
245 | }
246 |
247 | private String readableByteFormat(long bytes) {
248 | long kb = 1024;
249 | long mb = kb * 1024;
250 | long gb = mb * 1024;
251 |
252 | double value;
253 | int suffix;
254 | if (bytes >= gb) {
255 | value = (double) bytes / (double) gb;
256 | suffix = R.string.settings_content_disk_gb;
257 | } else if (bytes >= mb) {
258 | value = (double) bytes / (double) mb;
259 | suffix = R.string.settings_content_disk_mb;
260 | } else if (bytes >= kb) {
261 | value = (double) bytes / (double) kb;
262 | suffix = R.string.settings_content_disk_kb;
263 | } else {
264 | value = (double) bytes;
265 | suffix = R.string.settings_content_disk_b;
266 | }
267 | return getString(R.string.settings_content_disk_usage, value, getString(suffix));
268 | }
269 |
270 |
271 | @Override
272 | public void onAuthenticated(LayerClient layerClient, String s) {
273 | refresh();
274 | }
275 |
276 | @Override
277 | public void onDeauthenticated(LayerClient layerClient) {
278 | refresh();
279 | }
280 |
281 | @Override
282 | public void onAuthenticationChallenge(LayerClient layerClient, String s) {
283 |
284 | }
285 |
286 | @Override
287 | public void onAuthenticationError(LayerClient layerClient, LayerException e) {
288 |
289 | }
290 |
291 | @Override
292 | public void onConnectionConnected(LayerClient layerClient) {
293 | refresh();
294 | }
295 |
296 | @Override
297 | public void onConnectionDisconnected(LayerClient layerClient) {
298 | refresh();
299 | }
300 |
301 | @Override
302 | public void onConnectionError(LayerClient layerClient, LayerException e) {
303 |
304 | }
305 |
306 | @Override
307 | public void onChangeEvent(LayerChangeEvent layerChangeEvent) {
308 | refresh();
309 | }
310 |
311 | @Override
312 | public boolean onLongClick(View v) {
313 | if (v instanceof TextView) {
314 | Util.copyToClipboard(v.getContext(), R.string.settings_clipboard_description, ((TextView) v).getText().toString());
315 | Toast.makeText(this, R.string.toast_copied_to_clipboard, Toast.LENGTH_SHORT).show();
316 | return true;
317 | }
318 | return false;
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/app/src/main/java/com/layer/messenger/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.ActionBar;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.view.Menu;
7 | import android.view.MenuItem;
8 |
9 | import com.layer.atlas.provider.ParticipantProvider;
10 | import com.layer.sdk.LayerClient;
11 | import com.squareup.picasso.Picasso;
12 |
13 | public abstract class BaseActivity extends AppCompatActivity {
14 | private final int mLayoutResId;
15 | private final int mMenuResId;
16 | private final int mMenuTitleResId;
17 | private final boolean mMenuBackEnabled;
18 |
19 | public BaseActivity(int layoutResId, int menuResId, int menuTitleResId, boolean menuBackEnabled) {
20 | mLayoutResId = layoutResId;
21 | mMenuResId = menuResId;
22 | mMenuTitleResId = menuTitleResId;
23 | mMenuBackEnabled = menuBackEnabled;
24 | }
25 |
26 | @Override
27 | protected void onCreate(Bundle savedInstanceState) {
28 | super.onCreate(savedInstanceState);
29 | setContentView(mLayoutResId);
30 |
31 | ActionBar actionBar = getSupportActionBar();
32 | if (actionBar == null) return;
33 | if (mMenuBackEnabled) actionBar.setDisplayHomeAsUpEnabled(true);
34 | actionBar.setTitle(mMenuTitleResId);
35 | }
36 |
37 | @Override
38 | public void setTitle(CharSequence title) {
39 | ActionBar actionBar = getSupportActionBar();
40 | if (actionBar == null) {
41 | super.setTitle(title);
42 | } else {
43 | actionBar.setTitle(title);
44 | }
45 | }
46 |
47 | @Override
48 | public void setTitle(int titleId) {
49 | ActionBar actionBar = getSupportActionBar();
50 | if (actionBar == null) {
51 | super.setTitle(titleId);
52 | } else {
53 | actionBar.setTitle(titleId);
54 | }
55 | }
56 |
57 | @Override
58 | protected void onResume() {
59 | super.onResume();
60 | LayerClient client = App.getLayerClient();
61 | if (client == null) return;
62 | if (client.isAuthenticated()) {
63 | client.connect();
64 | } else {
65 | client.authenticate();
66 | }
67 | }
68 |
69 | @Override
70 | public boolean onCreateOptionsMenu(Menu menu) {
71 | getMenuInflater().inflate(mMenuResId, menu);
72 | return true;
73 | }
74 |
75 | @Override
76 | public boolean onOptionsItemSelected(MenuItem item) {
77 | if (item.getItemId() == android.R.id.home) {
78 | // Menu "Navigate Up" acts like hardware back button
79 | onBackPressed();
80 | return true;
81 | }
82 | return super.onOptionsItemSelected(item);
83 | }
84 |
85 | protected LayerClient getLayerClient() {
86 | return App.getLayerClient();
87 | }
88 |
89 | protected ParticipantProvider getParticipantProvider() {
90 | return App.getParticipantProvider();
91 | }
92 |
93 | protected Picasso getPicasso() {
94 | return App.getPicasso();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/layer/messenger/ConversationSettingsActivity.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger;
2 |
3 | import android.app.AlertDialog;
4 | import android.content.DialogInterface;
5 | import android.content.Intent;
6 | import android.net.Uri;
7 | import android.os.Bundle;
8 | import android.support.v7.widget.LinearLayoutManager;
9 | import android.support.v7.widget.RecyclerView;
10 | import android.view.KeyEvent;
11 | import android.view.LayoutInflater;
12 | import android.view.View;
13 | import android.view.ViewGroup;
14 | import android.view.inputmethod.EditorInfo;
15 | import android.widget.Button;
16 | import android.widget.CompoundButton;
17 | import android.widget.EditText;
18 | import android.widget.ImageView;
19 | import android.widget.LinearLayout;
20 | import android.widget.Switch;
21 | import android.widget.TextView;
22 | import android.widget.Toast;
23 |
24 | import com.layer.atlas.AtlasAvatar;
25 | import com.layer.atlas.provider.Participant;
26 | import com.layer.atlas.provider.ParticipantProvider;
27 | import com.layer.atlas.util.Util;
28 | import com.layer.sdk.LayerClient;
29 | import com.layer.sdk.changes.LayerChangeEvent;
30 | import com.layer.sdk.listeners.LayerChangeEventListener;
31 | import com.layer.sdk.listeners.LayerPolicyListener;
32 | import com.layer.sdk.messaging.Conversation;
33 | import com.layer.sdk.policy.Policy;
34 |
35 | import java.util.ArrayList;
36 | import java.util.Collections;
37 | import java.util.HashSet;
38 | import java.util.List;
39 | import java.util.Set;
40 |
41 | public class ConversationSettingsActivity extends BaseActivity implements LayerPolicyListener, LayerChangeEventListener {
42 | private EditText mConversationName;
43 | private Switch mShowNotifications;
44 | private RecyclerView mParticipantRecyclerView;
45 | private Button mLeaveButton;
46 | private Button mAddParticipantsButton;
47 |
48 | private Conversation mConversation;
49 | private ParticipantAdapter mParticipantAdapter;
50 |
51 | public ConversationSettingsActivity() {
52 | super(R.layout.activity_conversation_settings, R.menu.menu_conversation_details, R.string.title_conversation_details, true);
53 | }
54 |
55 | @Override
56 | protected void onCreate(Bundle savedInstanceState) {
57 | super.onCreate(savedInstanceState);
58 | mConversationName = (EditText) findViewById(R.id.conversation_name);
59 | mShowNotifications = (Switch) findViewById(R.id.show_notifications_switch);
60 | mParticipantRecyclerView = (RecyclerView) findViewById(R.id.participants);
61 | mLeaveButton = (Button) findViewById(R.id.leave_button);
62 | mAddParticipantsButton = (Button) findViewById(R.id.add_participant_button);
63 |
64 | // Get Conversation from Intent extras
65 | Uri conversationId = getIntent().getParcelableExtra(PushNotificationReceiver.LAYER_CONVERSATION_KEY);
66 | mConversation = getLayerClient().getConversation(conversationId);
67 | if (mConversation == null && !isFinishing()) finish();
68 |
69 | mParticipantAdapter = new ParticipantAdapter();
70 | mParticipantRecyclerView.setAdapter(mParticipantAdapter);
71 |
72 | LinearLayoutManager manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
73 | mParticipantRecyclerView.setLayoutManager(manager);
74 |
75 | mConversationName.setOnEditorActionListener(new TextView.OnEditorActionListener() {
76 | @Override
77 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
78 | if (actionId == EditorInfo.IME_ACTION_DONE || (event != null && event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
79 | String title = ((EditText) v).getText().toString().trim();
80 | Util.setConversationMetadataTitle(mConversation, title);
81 | Toast.makeText(v.getContext(), R.string.toast_group_name_updated, Toast.LENGTH_SHORT).show();
82 | return true;
83 | }
84 | return false;
85 | }
86 | });
87 |
88 | mShowNotifications.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
89 | @Override
90 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
91 | PushNotificationReceiver.getNotifications(ConversationSettingsActivity.this)
92 | .setEnabled(mConversation.getId(), isChecked);
93 | }
94 | });
95 |
96 | mLeaveButton.setOnClickListener(new View.OnClickListener() {
97 | @Override
98 | public void onClick(View v) {
99 | setEnabled(false);
100 | mConversation.removeParticipants(getLayerClient().getAuthenticatedUserId());
101 | refresh();
102 | Intent intent = new Intent(ConversationSettingsActivity.this, ConversationsListActivity.class);
103 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
104 | setEnabled(true);
105 | ConversationSettingsActivity.this.startActivity(intent);
106 | }
107 | });
108 |
109 | mAddParticipantsButton.setOnClickListener(new View.OnClickListener() {
110 | @Override
111 | public void onClick(View v) {
112 | // TODO
113 | Toast.makeText(v.getContext(), "Coming soon", Toast.LENGTH_LONG).show();
114 | }
115 | });
116 | }
117 |
118 | public void setEnabled(boolean enabled) {
119 | mShowNotifications.setEnabled(enabled);
120 | mLeaveButton.setEnabled(enabled);
121 | }
122 |
123 | private void refresh() {
124 | if (!getLayerClient().isAuthenticated()) return;
125 |
126 | mConversationName.setText(Util.getConversationMetadataTitle(mConversation));
127 | mShowNotifications.setChecked(PushNotificationReceiver.getNotifications(this).isEnabled(mConversation.getId()));
128 |
129 | Set participantsMinusMe = new HashSet(mConversation.getParticipants());
130 | participantsMinusMe.remove(getLayerClient().getAuthenticatedUserId());
131 |
132 | if (participantsMinusMe.size() == 0) {
133 | // I've been removed
134 | mConversationName.setEnabled(false);
135 | mLeaveButton.setVisibility(View.GONE);
136 | } else if (participantsMinusMe.size() == 1) {
137 | // 1-on-1
138 | mConversationName.setEnabled(false);
139 | mLeaveButton.setVisibility(View.GONE);
140 | } else {
141 | // Group
142 | mConversationName.setEnabled(true);
143 | mLeaveButton.setVisibility(View.VISIBLE);
144 | }
145 | mParticipantAdapter.refresh();
146 | }
147 |
148 | @Override
149 | protected void onResume() {
150 | super.onResume();
151 | getLayerClient().registerPolicyListener(this).registerEventListener(this);
152 | setEnabled(true);
153 | refresh();
154 | }
155 |
156 | @Override
157 | protected void onPause() {
158 | getLayerClient().unregisterPolicyListener(this).unregisterEventListener(this);
159 | super.onPause();
160 | }
161 |
162 | @Override
163 | public void onPolicyListUpdate(LayerClient layerClient, List list, List list1) {
164 | refresh();
165 | }
166 |
167 | @Override
168 | public void onChangeEvent(LayerChangeEvent layerChangeEvent) {
169 | refresh();
170 | }
171 |
172 | private class ParticipantAdapter extends RecyclerView.Adapter {
173 | List mParticipants = new ArrayList();
174 |
175 | public void refresh() {
176 | // Get new sorted list of Participants
177 | ParticipantProvider provider = App.getParticipantProvider();
178 | mParticipants.clear();
179 | for (String participantId : mConversation.getParticipants()) {
180 | if (participantId.equals(getLayerClient().getAuthenticatedUserId())) continue;
181 | Participant participant = provider.getParticipant(participantId);
182 | if (participant == null) continue;
183 | mParticipants.add(participant);
184 | }
185 | Collections.sort(mParticipants);
186 |
187 | // Adjust participant container height
188 | int height = Math.round(mParticipants.size() * getResources().getDimensionPixelSize(com.layer.atlas.R.dimen.atlas_secondary_item_height));
189 | LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height);
190 | mParticipantRecyclerView.setLayoutParams(params);
191 |
192 | // Notify changes
193 | notifyDataSetChanged();
194 | }
195 |
196 | @Override
197 | public ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
198 | ViewHolder viewHolder = new ViewHolder(parent);
199 | viewHolder.mAvatar.init(App.getParticipantProvider(), App.getPicasso());
200 | viewHolder.itemView.setTag(viewHolder);
201 |
202 | // Click to display remove / block dialog
203 | viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
204 | @Override
205 | public void onClick(View v) {
206 | final ViewHolder holder = (ViewHolder) v.getTag();
207 |
208 | AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext())
209 | .setMessage(holder.mTitle.getText().toString());
210 |
211 | if (mConversation.getParticipants().size() > 2) {
212 | builder.setNeutralButton(R.string.alert_button_remove, new DialogInterface.OnClickListener() {
213 | @Override
214 | public void onClick(DialogInterface dialog, int which) {
215 | mConversation.removeParticipants(holder.mParticipant.getId());
216 | }
217 | });
218 | }
219 |
220 | builder.setPositiveButton(holder.mBlockPolicy != null ? R.string.alert_button_unblock : R.string.alert_button_block,
221 | new DialogInterface.OnClickListener() {
222 | @Override
223 | public void onClick(DialogInterface dialog, int which) {
224 | Participant participant = holder.mParticipant;
225 | if (holder.mBlockPolicy == null) {
226 | // Block
227 | holder.mBlockPolicy = new Policy.Builder(Policy.PolicyType.BLOCK).sentByUserId(participant.getId()).build();
228 | getLayerClient().addPolicy(holder.mBlockPolicy);
229 | holder.mBlocked.setVisibility(View.VISIBLE);
230 | } else {
231 | getLayerClient().removePolicy(holder.mBlockPolicy);
232 | holder.mBlockPolicy = null;
233 | holder.mBlocked.setVisibility(View.INVISIBLE);
234 | }
235 | }
236 | }).setNegativeButton(R.string.alert_button_cancel, new DialogInterface.OnClickListener() {
237 | @Override
238 | public void onClick(DialogInterface dialog, int which) {
239 | dialog.dismiss();
240 | }
241 | }).show();
242 | }
243 | });
244 |
245 | return viewHolder;
246 | }
247 |
248 | @Override
249 | public void onBindViewHolder(ViewHolder viewHolder, int position) {
250 | Participant participant = mParticipants.get(position);
251 | viewHolder.mTitle.setText(participant.getName());
252 | viewHolder.mAvatar.setParticipants(participant.getId());
253 | viewHolder.mParticipant = participant;
254 |
255 | Policy block = null;
256 | for (Policy policy : getLayerClient().getPolicies()) {
257 | if (policy.getPolicyType() != Policy.PolicyType.BLOCK) continue;
258 | if (!policy.getSentByUserID().equals(participant.getId())) continue;
259 | block = policy;
260 | break;
261 | }
262 |
263 | viewHolder.mBlockPolicy = block;
264 | viewHolder.mBlocked.setVisibility(block == null ? View.INVISIBLE : View.VISIBLE);
265 | }
266 |
267 | @Override
268 | public int getItemCount() {
269 | return mParticipants.size();
270 | }
271 | }
272 |
273 | private static class ViewHolder extends RecyclerView.ViewHolder {
274 | AtlasAvatar mAvatar;
275 | TextView mTitle;
276 | ImageView mBlocked;
277 | Participant mParticipant;
278 | Policy mBlockPolicy;
279 |
280 | public ViewHolder(ViewGroup parent) {
281 | super(LayoutInflater.from(parent.getContext()).inflate(R.layout.participant_item, parent, false));
282 | mAvatar = (AtlasAvatar) itemView.findViewById(R.id.avatar);
283 | mTitle = (TextView) itemView.findViewById(R.id.title);
284 | mBlocked = (ImageView) itemView.findViewById(R.id.blocked);
285 | }
286 | }
287 | }
288 |
--------------------------------------------------------------------------------
/app/src/main/java/com/layer/messenger/ConversationsListActivity.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger;
2 |
3 | import android.app.AlertDialog;
4 | import android.content.DialogInterface;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.view.MenuItem;
8 | import android.view.View;
9 |
10 | import com.layer.atlas.AtlasConversationsRecyclerView;
11 | import com.layer.atlas.adapters.AtlasConversationsAdapter;
12 | import com.layer.atlas.util.views.SwipeableItem;
13 | import com.layer.messenger.makemoji.MakeMojiConversationsAdapter;
14 | import com.layer.messenger.makemoji.MakeMojiConversationsRecyclerView;
15 | import com.layer.messenger.util.Log;
16 | import com.layer.sdk.LayerClient;
17 | import com.layer.sdk.messaging.Conversation;
18 |
19 | public class ConversationsListActivity extends BaseActivity {
20 | public ConversationsListActivity() {
21 | super(R.layout.activity_conversations_list, R.menu.menu_conversations_list, R.string.title_conversations_list, false);
22 | }
23 |
24 | @Override
25 | protected void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | if (App.routeLogin(this)) {
28 | if (!isFinishing()) finish();
29 | return;
30 | }
31 |
32 | final MakeMojiConversationsRecyclerView conversationsList = (MakeMojiConversationsRecyclerView) findViewById(R.id.conversations_list);
33 |
34 | // Atlas methods
35 | conversationsList.init(getLayerClient(), getParticipantProvider(), getPicasso())
36 | .setInitialHistoricMessagesToFetch(20)
37 | .setOnConversationClickListener(new MakeMojiConversationsAdapter.OnConversationClickListener() {
38 | @Override
39 | public void onConversationClick(MakeMojiConversationsAdapter adapter, Conversation conversation) {
40 | Intent intent = new Intent(ConversationsListActivity.this, MessagesListActivity.class);
41 | if (Log.isLoggable(Log.VERBOSE)) {
42 | Log.v("Launching MessagesListActivity with existing conversation ID: " + conversation.getId());
43 | }
44 | intent.putExtra(PushNotificationReceiver.LAYER_CONVERSATION_KEY, conversation.getId());
45 | startActivity(intent);
46 | }
47 |
48 | @Override
49 | public boolean onConversationLongClick(MakeMojiConversationsAdapter adapter, Conversation conversation) {
50 | return false;
51 | }
52 | })
53 | .setOnConversationSwipeListener(new SwipeableItem.OnSwipeListener() {
54 | @Override
55 | public void onSwipe(final Conversation conversation, int direction) {
56 | new AlertDialog.Builder(ConversationsListActivity.this)
57 | .setMessage(R.string.alert_message_delete_conversation)
58 | .setNegativeButton(R.string.alert_button_cancel, new DialogInterface.OnClickListener() {
59 | @Override
60 | public void onClick(DialogInterface dialog, int which) {
61 | // TODO: simply update this one conversation
62 | conversationsList.getAdapter().notifyDataSetChanged();
63 | dialog.dismiss();
64 | }
65 | })
66 | .setNeutralButton(R.string.alert_button_delete_my_devices, new DialogInterface.OnClickListener() {
67 | @Override
68 | public void onClick(DialogInterface dialog, int which) {
69 | conversation.delete(LayerClient.DeletionMode.ALL_MY_DEVICES);
70 | }
71 | })
72 | .setPositiveButton(R.string.alert_button_delete_all_participants, new DialogInterface.OnClickListener() {
73 | @Override
74 | public void onClick(DialogInterface dialog, int which) {
75 | conversation.delete(LayerClient.DeletionMode.ALL_PARTICIPANTS);
76 | }
77 | })
78 | .show();
79 | }
80 | });
81 |
82 | findViewById(R.id.floating_action_button)
83 | .setOnClickListener(new View.OnClickListener() {
84 | public void onClick(View v) {
85 | startActivity(new Intent(ConversationsListActivity.this, MessagesListActivity.class));
86 | }
87 | });
88 | }
89 |
90 | @Override
91 | public boolean onOptionsItemSelected(MenuItem item) {
92 | switch (item.getItemId()) {
93 | case R.id.action_settings:
94 | startActivity(new Intent(this, AppSettingsActivity.class));
95 | return true;
96 |
97 | case R.id.action_sendlogs:
98 | LayerClient.sendLogs(getLayerClient(), this);
99 | return true;
100 | }
101 |
102 | return super.onOptionsItemSelected(item);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/app/src/main/java/com/layer/messenger/ResumeActivity.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.v7.app.ActionBar;
7 | import android.support.v7.app.AppCompatActivity;
8 |
9 | import com.layer.messenger.util.Log;
10 | import com.layer.sdk.LayerClient;
11 | import com.layer.sdk.exceptions.LayerException;
12 | import com.layer.sdk.listeners.LayerAuthenticationListener;
13 |
14 | import java.util.concurrent.atomic.AtomicReference;
15 |
16 | public class ResumeActivity extends AppCompatActivity implements LayerAuthenticationListener {
17 | public static final String EXTRA_LOGGED_IN_ACTIVITY_CLASS_NAME = "loggedInActivity";
18 | public static final String EXTRA_LOGGED_OUT_ACTIVITY_CLASS_NAME = "loggedOutActivity";
19 |
20 | private AtomicReference> mLoggedInActivity = new AtomicReference>(null);
21 | private AtomicReference> mLoggedOutActivity = new AtomicReference>(null);
22 |
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | setContentView(R.layout.activity_resume);
27 | final ActionBar actionBar = getSupportActionBar();
28 | if (actionBar != null) actionBar.hide();
29 | }
30 |
31 | @SuppressWarnings("unchecked")
32 | @Override
33 | protected void onResume() {
34 | super.onResume();
35 | App.getLayerClient().registerAuthenticationListener(this).authenticate();
36 | try {
37 | mLoggedInActivity.set((Class extends Activity>) Class.forName(getIntent().getStringExtra(EXTRA_LOGGED_IN_ACTIVITY_CLASS_NAME)));
38 | mLoggedOutActivity.set((Class extends Activity>) Class.forName(getIntent().getStringExtra(EXTRA_LOGGED_OUT_ACTIVITY_CLASS_NAME)));
39 | } catch (ClassNotFoundException e) {
40 | if (Log.isLoggable(Log.ERROR)) {
41 | Log.e("Could not find class: " + e.getCause(), e);
42 | }
43 | }
44 | }
45 |
46 | @Override
47 | protected void onPause() {
48 | App.getLayerClient().unregisterAuthenticationListener(this);
49 | super.onPause();
50 | }
51 |
52 | @Override
53 | public void onAuthenticated(LayerClient layerClient, String s) {
54 | startActivity(mLoggedInActivity.get());
55 | }
56 |
57 | @Override
58 | public void onDeauthenticated(LayerClient layerClient) {
59 | startActivity(mLoggedOutActivity.get());
60 | }
61 |
62 | @Override
63 | public void onAuthenticationChallenge(LayerClient layerClient, String s) {
64 |
65 | }
66 |
67 | @Override
68 | public void onAuthenticationError(LayerClient layerClient, LayerException e) {
69 | startActivity(mLoggedOutActivity.get());
70 | }
71 |
72 | private void startActivity(Class extends Activity> activityClass) {
73 | Intent intent = new Intent(this, activityClass);
74 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
75 | startActivity(intent);
76 | }
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/layer/messenger/makemoji/MakeMojiCellFactory.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.makemoji;
2 |
3 | import android.content.Context;
4 | import android.graphics.drawable.GradientDrawable;
5 | import android.text.Spanned;
6 | import android.util.TypedValue;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.TextView;
11 | import android.widget.Toast;
12 |
13 | import com.layer.atlas.messagetypes.AtlasCellFactory;
14 | import com.layer.atlas.provider.Participant;
15 | import com.layer.atlas.provider.ParticipantProvider;
16 | import com.layer.atlas.util.Util;
17 | import com.layer.sdk.LayerClient;
18 | import com.layer.sdk.messaging.Actor;
19 | import com.layer.sdk.messaging.Message;
20 | import com.layer.sdk.messaging.MessagePart;
21 |
22 | import com.makemoji.mojilib.HyperMojiListener;
23 | import com.makemoji.mojilib.Moji;
24 | import com.makemoji.mojilib.ParsedAttributes;
25 |
26 | public class MakeMojiCellFactory extends AtlasCellFactory implements View.OnLongClickListener {
27 | public final static String MIME_TYPE = "text/plain";
28 | HyperMojiListener hyperMojiListener;
29 |
30 | public MakeMojiCellFactory(HyperMojiListener hyperMojiListener) {
31 | super(256 * 1024);
32 | this.hyperMojiListener = hyperMojiListener;
33 | }
34 |
35 | public static boolean isType(Message message) {
36 | return message.getMessageParts().get(0).getMimeType().equals(MIME_TYPE);
37 | }
38 |
39 | public static String getMessagePreview(Context context, Message message) {
40 | MessagePart part = message.getMessageParts().get(0);
41 | // For large text content, the MessagePart may not be downloaded yet.
42 | return part.isContentReady() ? new String(part.getData()) : "";
43 | }
44 |
45 | public static void setMessagePreview(TextView textView, Message message){
46 | MessagePart part = message.getMessageParts().get(0);
47 | String html = part.isContentReady() ? new String(part.getData()) : "";
48 | Moji.setText(html,textView,true);
49 | }
50 | @Override
51 | public boolean isBindable(Message message) {
52 | return MakeMojiCellFactory.isType(message);
53 | }
54 |
55 | @SuppressWarnings("ResourceAsColor")
56 | @Override
57 | public CellHolder createCellHolder(ViewGroup cellView, boolean isMe, LayoutInflater layoutInflater) {
58 | View v = layoutInflater.inflate(com.layer.atlas.R.layout.atlas_message_item_cell_text, cellView, true);
59 | v.setBackgroundResource(isMe ? com.layer.atlas.R.drawable.atlas_message_item_cell_me : com.layer.atlas.R.drawable.atlas_message_item_cell_them);
60 | ((GradientDrawable) v.getBackground()).setColor(isMe ? mMessageStyle.getMyBubbleColor() : mMessageStyle.getOtherBubbleColor());
61 |
62 | TextView t = (TextView) v.findViewById(com.layer.atlas.R.id.cell_text);
63 | t.setTextSize(TypedValue.COMPLEX_UNIT_PX, isMe ? mMessageStyle.getMyTextSize() : mMessageStyle.getOtherTextSize());
64 | t.setTextColor(isMe ? mMessageStyle.getMyTextColor() : mMessageStyle.getOtherTextColor());
65 | t.setLinkTextColor(isMe ? mMessageStyle.getMyTextColor() : mMessageStyle.getOtherTextColor());
66 | t.setTypeface(isMe ? mMessageStyle.getMyTextTypeface() : mMessageStyle.getOtherTextTypeface(), isMe ? mMessageStyle.getMyTextStyle() : mMessageStyle.getOtherTextStyle());
67 | return new CellHolder(v,hyperMojiListener);
68 | }
69 |
70 | @Override
71 | public TextInfo parseContent(LayerClient layerClient, ParticipantProvider participantProvider, Message message) {
72 | MessagePart part = message.getMessageParts().get(0);
73 | String html = part.isContentReady() ? new String(part.getData()) : "";
74 | ParsedAttributes parsedAttributes = Moji.parseHtml(html,null,true); //also contains size, color.
75 | String name;
76 | Actor sender = message.getSender();
77 | if (sender.getName() != null) {
78 | name = sender.getName() + ": ";
79 | } else {
80 | Participant participant = participantProvider.getParticipant(sender.getUserId());
81 | name = participant == null ? "" : (participant.getName() + ": ");
82 | }
83 | return new TextInfo(html,parsedAttributes.spanned, name);
84 | }
85 |
86 | @Override
87 | public void bindCellHolder(CellHolder cellHolder, final TextInfo parsed, Message message, CellHolderSpecs specs) {
88 | // Moji.setText(parsed.getSpanned(),cellHolder.mTextView);
89 | MessagePart part = message.getMessageParts().get(0);
90 | String text = part.isContentReady() ? new String(part.getData()) : "";
91 | Moji.setText(text,cellHolder.mTextView,true);
92 | cellHolder.mTextView.setTag(parsed);
93 | cellHolder.mTextView.setOnLongClickListener(this);
94 | }
95 |
96 | /**
97 | * Long click copies message text and sender name to clipboard
98 | */
99 | @Override
100 | public boolean onLongClick(View v) {
101 | TextInfo parsed = (TextInfo) v.getTag();
102 | String text = parsed.getmHtml();
103 | Util.copyToClipboard(v.getContext(), com.layer.atlas.R.string.atlas_text_cell_factory_clipboard_description, text);
104 | Toast.makeText(v.getContext(), com.layer.atlas.R.string.atlas_text_cell_factory_copied_to_clipboard, Toast.LENGTH_SHORT).show();
105 | return true;
106 | }
107 |
108 | public static class CellHolder extends AtlasCellFactory.CellHolder {
109 | TextView mTextView;
110 |
111 | public CellHolder(View view,HyperMojiListener hyperMojiListener) {
112 | mTextView = (TextView) view.findViewById(com.layer.atlas.R.id.cell_text);
113 | mTextView.setTag(com.makemoji.mojilib.R.id._makemoji_hypermoji_listener_tag_id,hyperMojiListener);
114 | }
115 | }
116 |
117 | public static class TextInfo implements AtlasCellFactory.ParsedContent {
118 | private final String mHtml;
119 | private final Spanned mSpanned;
120 | private final String mClipboardPrefix;
121 | private final int mSize;
122 |
123 | public TextInfo(String html,Spanned spanned, String clipboardPrefix) {
124 | mHtml = html;
125 | mSpanned = spanned;
126 | mClipboardPrefix = clipboardPrefix;
127 | mSize = mSpanned.toString().getBytes().length + mClipboardPrefix.getBytes().length;
128 | }
129 |
130 | public Spanned getSpanned() {
131 | return mSpanned;
132 | }
133 |
134 | public String getmHtml(){return mHtml;}
135 |
136 | public String getClipboardPrefix() {
137 | return mClipboardPrefix;
138 | }
139 |
140 | @Override
141 | public int sizeOf() {
142 | return mSize;
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/app/src/main/java/com/layer/messenger/makemoji/MakeMojiConversationsRecyclerView.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.makemoji;
2 |
3 | /**
4 | * Created by s_baa on 5/30/2016.
5 | */
6 | /*
7 | * Copyright (c) 2015 Layer. All rights reserved.
8 | *
9 | * Licensed under the Apache License, Version 2.0 (the "License");
10 | * you may not use this file except in compliance with the License.
11 | * You may obtain a copy of the License at
12 | *
13 | * http://www.apache.org/licenses/LICENSE-2.0
14 | *
15 | * Unless required by applicable law or agreed to in writing, software
16 | * distributed under the License is distributed on an "AS IS" BASIS,
17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | * See the License for the specific language governing permissions and
19 | * limitations under the License.
20 | */
21 |
22 | import android.content.Context;
23 | import android.content.res.TypedArray;
24 | import android.graphics.Color;
25 | import android.graphics.Typeface;
26 | import android.support.v7.widget.LinearLayoutManager;
27 | import android.support.v7.widget.RecyclerView;
28 | import android.support.v7.widget.helper.ItemTouchHelper;
29 | import android.util.AttributeSet;
30 | import android.view.View;
31 |
32 | import com.layer.atlas.provider.ParticipantProvider;
33 | import com.layer.atlas.util.AvatarStyle;
34 | import com.layer.atlas.util.ConversationStyle;
35 | import com.layer.atlas.util.itemanimators.NoChangeAnimator;
36 | import com.layer.atlas.util.views.SwipeableItem;
37 | import com.layer.sdk.LayerClient;
38 | import com.layer.sdk.messaging.Conversation;
39 | import com.squareup.picasso.Picasso;
40 |
41 |
42 | public class MakeMojiConversationsRecyclerView extends RecyclerView {
43 | MakeMojiConversationsAdapter mAdapter;
44 | private ItemTouchHelper mSwipeItemTouchHelper;
45 |
46 | private ConversationStyle conversationStyle;
47 |
48 | public MakeMojiConversationsRecyclerView(Context context, AttributeSet attrs, int defStyle) {
49 | super(context, attrs, defStyle);
50 | parseStyle(context, attrs, defStyle);
51 | }
52 |
53 | public MakeMojiConversationsRecyclerView(Context context, AttributeSet attrs) {
54 | this(context, attrs, 0);
55 | }
56 |
57 | public MakeMojiConversationsRecyclerView(Context context) {
58 | super(context);
59 | }
60 |
61 | public MakeMojiConversationsRecyclerView init(LayerClient layerClient, ParticipantProvider participantProvider, Picasso picasso) {
62 | // Linear layout manager
63 | LinearLayoutManager manager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
64 | manager.setStackFromEnd(false);
65 | setLayoutManager(manager);
66 |
67 | // Don't flash items when changing content
68 | setItemAnimator(new NoChangeAnimator());
69 |
70 | mAdapter = new MakeMojiConversationsAdapter(getContext(), layerClient, participantProvider, picasso);
71 | mAdapter.setStyle(conversationStyle);
72 | super.setAdapter(mAdapter);
73 |
74 | return this;
75 | }
76 |
77 | @Override
78 | public void setAdapter(Adapter adapter) {
79 | throw new RuntimeException("AtlasConversationsRecyclerView sets its own Adapter");
80 | }
81 |
82 | /**
83 | * Automatically refresh on resume
84 | */
85 | @Override
86 | protected void onVisibilityChanged(View changedView, int visibility) {
87 | super.onVisibilityChanged(changedView, visibility);
88 | if (visibility != View.VISIBLE) return;
89 | refresh();
90 | }
91 |
92 | public MakeMojiConversationsRecyclerView refresh() {
93 | if (mAdapter != null) mAdapter.refresh();
94 | return this;
95 | }
96 |
97 | /**
98 | * Convenience pass-through to this list's AtlasConversationsAdapter.
99 | *
100 | * @see MakeMojiConversationsAdapter#setOnConversationClickListener(MakeMojiConversationsAdapter.OnConversationClickListener)
101 | */
102 | public MakeMojiConversationsRecyclerView setOnConversationClickListener(MakeMojiConversationsAdapter.OnConversationClickListener listener) {
103 | mAdapter.setOnConversationClickListener(listener);
104 | return this;
105 | }
106 |
107 | public MakeMojiConversationsRecyclerView setOnConversationSwipeListener(SwipeableItem.OnSwipeListener listener) {
108 | if (mSwipeItemTouchHelper != null) {
109 | mSwipeItemTouchHelper.attachToRecyclerView(null);
110 | }
111 | if (listener == null) {
112 | mSwipeItemTouchHelper = null;
113 | } else {
114 | listener.setBaseAdapter((MakeMojiConversationsAdapter) getAdapter());
115 | mSwipeItemTouchHelper = new ItemTouchHelper(listener);
116 | mSwipeItemTouchHelper.attachToRecyclerView(this);
117 | }
118 | return this;
119 | }
120 |
121 | /**
122 | * Convenience pass-through to this list's AtlasConversationsAdapter.
123 | *
124 | * @see MakeMojiConversationsAdapter#setInitialHistoricMessagesToFetch(long)
125 | */
126 | public MakeMojiConversationsRecyclerView setInitialHistoricMessagesToFetch(long count) {
127 | mAdapter.setInitialHistoricMessagesToFetch(count);
128 | return this;
129 | }
130 |
131 | public MakeMojiConversationsRecyclerView setTypeface(Typeface titleTypeface, Typeface titleUnreadTypeface, Typeface subtitleTypeface, Typeface subtitleUnreadTypeface, Typeface dateTypeface) {
132 | conversationStyle.setTitleTextTypeface(titleTypeface);
133 | conversationStyle.setTitleUnreadTextTypeface(titleUnreadTypeface);
134 | conversationStyle.setSubtitleTextTypeface(subtitleTypeface);
135 | conversationStyle.setSubtitleUnreadTextTypeface(subtitleUnreadTypeface);
136 | conversationStyle.setDateTextTypeface(dateTypeface);
137 | return this;
138 | }
139 |
140 | private void parseStyle(Context context, AttributeSet attrs, int defStyle) {
141 | ConversationStyle.Builder styleBuilder = new ConversationStyle.Builder();
142 | TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, com.layer.atlas.R.styleable.AtlasConversationsRecyclerView, com.layer.atlas.R.attr.AtlasConversationsRecyclerView, defStyle);
143 | styleBuilder.titleTextColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellTitleTextColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_text_gray)));
144 | int titleTextStyle = ta.getInt(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellTitleTextStyle, Typeface.NORMAL);
145 | styleBuilder.titleTextStyle(titleTextStyle);
146 | String titleTextTypefaceName = ta.getString(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellTitleTextTypeface);
147 | styleBuilder.titleTextTypeface(titleTextTypefaceName != null ? Typeface.create(titleTextTypefaceName, titleTextStyle) : null);
148 |
149 | styleBuilder.titleUnreadTextColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellTitleUnreadTextColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_text_black)));
150 | int titleUnreadTextStyle = ta.getInt(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellTitleUnreadTextStyle, Typeface.BOLD);
151 | styleBuilder.titleUnreadTextStyle(titleUnreadTextStyle);
152 | String titleUnreadTextTypefaceName = ta.getString(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellTitleUnreadTextTypeface);
153 | styleBuilder.titleUnreadTextTypeface(titleUnreadTextTypefaceName != null ? Typeface.create(titleUnreadTextTypefaceName, titleUnreadTextStyle) : null);
154 |
155 | styleBuilder.subtitleTextColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellSubtitleTextColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_text_gray)));
156 | int subtitleTextStyle = ta.getInt(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellSubtitleTextStyle, Typeface.NORMAL);
157 | styleBuilder.subtitleTextStyle(subtitleTextStyle);
158 | String subtitleTextTypefaceName = ta.getString(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellSubtitleTextTypeface);
159 | styleBuilder.subtitleTextTypeface(subtitleTextTypefaceName != null ? Typeface.create(subtitleTextTypefaceName, subtitleTextStyle) : null);
160 |
161 | styleBuilder.subtitleUnreadTextColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellSubtitleUnreadTextColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_text_black)));
162 | int subtitleUnreadTextStyle = ta.getInt(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellSubtitleUnreadTextStyle, Typeface.NORMAL);
163 | styleBuilder.subtitleUnreadTextStyle(subtitleUnreadTextStyle);
164 | String subtitleUnreadTextTypefaceName = ta.getString(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellSubtitleUnreadTextTypeface);
165 | styleBuilder.subtitleUnreadTextTypeface(subtitleUnreadTextTypefaceName != null ? Typeface.create(subtitleUnreadTextTypefaceName, subtitleUnreadTextStyle) : null);
166 |
167 | styleBuilder.cellBackgroundColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellBackgroundColor, Color.TRANSPARENT));
168 | styleBuilder.cellUnreadBackgroundColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellUnreadBackgroundColor, Color.TRANSPARENT));
169 | styleBuilder.dateTextColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_dateTextColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_color_primary_blue)));
170 |
171 | AvatarStyle.Builder avatarStyleBuilder = new AvatarStyle.Builder();
172 | avatarStyleBuilder.avatarTextColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_avatarTextColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_avatar_text)));
173 | avatarStyleBuilder.avatarBackgroundColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_avatarBackgroundColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_avatar_background)));
174 | avatarStyleBuilder.avatarBorderColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_avatarBorderColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_avatar_border)));
175 | styleBuilder.avatarStyle(avatarStyleBuilder.build());
176 | ta.recycle();
177 | conversationStyle = styleBuilder.build();
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/app/src/main/java/com/layer/messenger/util/AuthenticationProvider.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.util;
2 |
3 | import android.app.Activity;
4 |
5 | import com.layer.sdk.LayerClient;
6 | import com.layer.sdk.listeners.LayerAuthenticationListener;
7 |
8 | /**
9 | * AuthenticationProvider implementations authenticate users with backend "Identity Providers."
10 | *
11 | * @param Session credentials for this AuthenticationProvider used to resume an
12 | * authenticated session.
13 | */
14 | public interface AuthenticationProvider extends LayerAuthenticationListener.BackgroundThread.Weak {
15 |
16 | /**
17 | * Sets this AuthenticationProvider's credentials. Credentials should be cached to handle
18 | * future authentication challenges. When `credentials` is `null`, the cached credentials
19 | * should be cleared.
20 | *
21 | * @param credentials Credentials to cache.
22 | * @return This AuthenticationProvider.
23 | */
24 | AuthenticationProvider setCredentials(Tcredentials credentials);
25 |
26 |
27 | /**
28 | * Returns `true` if this AuthenticationProvider has cached credentials, or `false` otherwise.
29 | *
30 | * @return `true` if this AuthenticationProvider has cached credentials, or `false` otherwise.
31 | */
32 | boolean hasCredentials();
33 |
34 | /**
35 | * Sets the authentication callback for reporting authentication success and failure.
36 | *
37 | * @param callback Callback to receive authentication success and failure.
38 | * @return This AuthenticationProvider.
39 | */
40 | AuthenticationProvider setCallback(Callback callback);
41 |
42 | /**
43 | * Routes the user to a login screen if required. If routing, return `true` and start the
44 | * desired login Activity.
45 | *
46 | * @param layerClient
47 | * @param layerAppId
48 | * @param from
49 | * @return
50 | */
51 | boolean routeLogin(LayerClient layerClient, String layerAppId, Activity from);
52 |
53 | /**
54 | * Callback for handling authentication success and failure.
55 | */
56 | interface Callback {
57 | void onSuccess(AuthenticationProvider provider, String userId);
58 |
59 | void onError(AuthenticationProvider provider, String error);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/com/layer/messenger/util/Log.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.util;
2 |
3 | /**
4 | * Unified Log class used by Atlas Messenger classes that maintains similar signatures to
5 | * `android.util.Log`. Logs are tagged with `Atlas`.
6 | */
7 | public class Log {
8 | public static final String TAG = "LayerAtlasMsgr";
9 |
10 | // Makes IDE auto-completion easy
11 | public static final int VERBOSE = android.util.Log.VERBOSE;
12 | public static final int DEBUG = android.util.Log.DEBUG;
13 | public static final int INFO = android.util.Log.INFO;
14 | public static final int WARN = android.util.Log.WARN;
15 | public static final int ERROR = android.util.Log.ERROR;
16 |
17 | private static volatile boolean sAlwaysLoggable = false;
18 |
19 | /**
20 | * Returns `true` if the provided log level is loggable either through environment options or
21 | * a previous call to setAlwaysLoggable().
22 | *
23 | * @param level Log level to check.
24 | * @return `true` if the provided log level is loggable.
25 | * @see #setAlwaysLoggable(boolean)
26 | */
27 | public static boolean isLoggable(int level) {
28 | return sAlwaysLoggable || android.util.Log.isLoggable(TAG, level);
29 | }
30 |
31 | public static void setAlwaysLoggable(boolean alwaysOn) {
32 | sAlwaysLoggable = alwaysOn;
33 | }
34 |
35 | public static void v(String message) {
36 | android.util.Log.v(TAG, message);
37 | }
38 |
39 | public static void v(String message, Throwable error) {
40 | android.util.Log.v(TAG, message, error);
41 | }
42 |
43 | public static void d(String message) {
44 | android.util.Log.d(TAG, message);
45 | }
46 |
47 | public static void d(String message, Throwable error) {
48 | android.util.Log.d(TAG, message, error);
49 | }
50 |
51 | public static void i(String message) {
52 | android.util.Log.i(TAG, message);
53 | }
54 |
55 | public static void i(String message, Throwable error) {
56 | android.util.Log.i(TAG, message, error);
57 | }
58 |
59 | public static void w(String message) {
60 | android.util.Log.w(TAG, message);
61 | }
62 |
63 | public static void w(String message, Throwable error) {
64 | android.util.Log.w(TAG, message, error);
65 | }
66 |
67 | public static void w(Throwable error) {
68 | android.util.Log.w(TAG, error);
69 | }
70 |
71 | public static void e(String message) {
72 | android.util.Log.e(TAG, message);
73 | }
74 |
75 | public static void e(String message, Throwable error) {
76 | android.util.Log.e(TAG, message, error);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/app/src/main/java/com/layer/messenger/util/Util.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.util;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.io.InputStreamReader;
6 | import java.io.StringWriter;
7 |
8 | public class Util {
9 | public static String streamToString(InputStream stream) throws IOException {
10 | int n = 0;
11 | char[] buffer = new char[1024 * 4];
12 | InputStreamReader reader = new InputStreamReader(stream, "UTF8");
13 | StringWriter writer = new StringWriter();
14 | while (-1 != (n = reader.read(buffer))) writer.write(buffer, 0, n);
15 | return writer.toString();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_block_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-hdpi/ic_block_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-hdpi/notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-ldpi/notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-ldpi/notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_block_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-mdpi/ic_block_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-mdpi/notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/layer_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-nodpi/layer_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_block_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-xhdpi/ic_block_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-xhdpi/notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_block_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-xxhdpi/ic_block_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-xxhdpi/notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_block_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-xxxhdpi/ic_block_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-xxxhdpi/notification.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_conversation_settings.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
9 |
10 |
15 |
16 |
22 |
23 |
24 |
29 |
30 |
39 |
40 |
41 |
42 |
50 |
51 |
58 |
59 |
64 |
65 |
66 |
67 |
76 |
77 |
81 |
82 |
87 |
88 |
94 |
95 |
96 |
100 |
101 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_conversations_list.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
9 |
10 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_messages_list.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
10 |
15 |
16 |
20 |
21 |
22 |
23 |
29 |
30 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_resume.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
15 |
16 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/atlas_message_composer.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
21 |
22 |
32 |
33 |
46 |
47 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/participant_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
19 |
20 |
30 |
31 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_conversation_details.xml:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_conversations_list.xml:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_messages_list.xml:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_settings.xml:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Settings
5 | Conversation Details
6 | Send Debug Info
7 |
8 | Conversations
9 | New Message
10 | Messages
11 | Settings
12 | Details
13 |
14 | Resuming Session
15 |
16 | Log out?
17 | Delete message?
18 | Delete conversation?
19 |
20 | Logging Out…
21 |
22 | Cancel
23 | For Me
24 | From All
25 | Remove
26 | Block
27 | Unblock
28 | Log Out
29 |
30 | Camera
31 | Gallery
32 | Location
33 |
34 | Group name updated
35 | Failed to deauthenticate: %1$s
36 | Copied to clipboard
37 |
38 | Options
39 | Group Name
40 | Show Notifications
41 | Leave Group
42 | Participants
43 | Add Participants
44 |
45 | Settings
46 | Connected
47 | Disconnected
48 | Name %1$s / Code %2$d
49 | Version %1$s / API level %2$d
50 | Unlimited
51 | %1$.2f %2$s
52 | GB
53 | MB
54 | KB
55 | B
56 | Account
57 | Log Out
58 | Notifications
59 | Show Notifications
60 | Debug
61 | Verbose Logging
62 | App Version
63 | Android Version
64 | Atlas Version
65 | Layer Version
66 | User ID
67 | Statistics
68 | Conversations
69 | Messages
70 | Unread Messages
71 | Rich Content
72 | Disk Utilization
73 | Disk Allowance
74 | Auto-Download MIME Types
75 |
76 | +%1$d more
77 | %1$d new messages
78 |
79 | New message (tap to sync)
80 |
81 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/providerdemo/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/providerdemo/java/com/layer/messenger/flavor/AppIdScanner.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.flavor;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageManager;
5 | import android.content.res.Configuration;
6 | import android.net.Uri;
7 | import android.support.v4.app.ActivityCompat;
8 | import android.util.AttributeSet;
9 | import android.util.SparseArray;
10 | import android.view.SurfaceHolder;
11 | import android.view.SurfaceView;
12 | import android.view.ViewGroup;
13 |
14 | import com.google.android.gms.common.images.Size;
15 | import com.google.android.gms.vision.CameraSource;
16 | import com.google.android.gms.vision.Detector;
17 | import com.google.android.gms.vision.barcode.Barcode;
18 | import com.google.android.gms.vision.barcode.BarcodeDetector;
19 | import com.layer.messenger.util.Log;
20 |
21 | import java.io.IOException;
22 | import java.util.List;
23 | import java.util.UUID;
24 | import java.util.concurrent.TimeUnit;
25 |
26 | public class AppIdScanner extends ViewGroup {
27 | private SurfaceView mSurfaceView;
28 | private BarcodeDetector mBarcodeDetector;
29 | private Detector.Processor mAppIdProcessor;
30 | private CameraSource.Builder mCameraBuilder;
31 |
32 | private AppIdCallback mAppIdCallback;
33 | private boolean mStartRequested;
34 | private boolean mSurfaceAvailable;
35 | private CameraSource mCameraSource;
36 |
37 | public AppIdScanner(Context context) {
38 | super(context);
39 | init();
40 | }
41 |
42 | public AppIdScanner(Context context, AttributeSet attrs) {
43 | this(context, attrs, 0);
44 | }
45 |
46 | public AppIdScanner(Context context, AttributeSet attrs, int defStyleAttr) {
47 | super(context, attrs, defStyleAttr);
48 | init();
49 | }
50 |
51 | private void init() {
52 | mStartRequested = false;
53 | mSurfaceAvailable = false;
54 |
55 | mAppIdProcessor = new Detector.Processor() {
56 | @Override
57 | public void release() {
58 |
59 | }
60 |
61 | @Override
62 | public void receiveDetections(Detector.Detections detections) {
63 | SparseArray barcodes = detections.getDetectedItems();
64 | for (int i = 0; i < barcodes.size(); i++) {
65 | Barcode barcode = barcodes.valueAt(i);
66 | String value = barcode.displayValue;
67 | try {
68 | Uri appId = Uri.parse(value);
69 | if (!appId.getScheme().equals("layer")) {
70 | throw new IllegalArgumentException("URI is not an App ID");
71 | }
72 | if (!appId.getAuthority().equals("")) {
73 | throw new IllegalArgumentException("URI is not an App ID");
74 | }
75 | List segments = appId.getPathSegments();
76 | if (segments.size() != 3) {
77 | throw new IllegalArgumentException("URI is not an App ID");
78 | }
79 | if (!segments.get(0).equals("apps")) {
80 | throw new IllegalArgumentException("URI is not an App ID");
81 | }
82 | if (!segments.get(1).equals("staging") && !segments.get(1).equals("production")) {
83 | throw new IllegalArgumentException("URI is not an App ID");
84 | }
85 | UUID uuid = UUID.fromString(segments.get(2));
86 | if (Log.isLoggable(Log.VERBOSE)) {
87 | Log.v("Captured Layer App ID: " + appId + ", UUID: " + uuid);
88 | }
89 | if (mAppIdCallback == null) return;
90 | mAppIdCallback.onLayerAppIdScanned(AppIdScanner.this, appId.toString());
91 | } catch (Exception e) {
92 | // Not this barcode...
93 | if (Log.isLoggable(Log.ERROR)) {
94 | Log.e("Barcode does not contain an App ID URI: " + value, e);
95 | }
96 | }
97 | }
98 | }
99 | };
100 |
101 | mBarcodeDetector = new BarcodeDetector.Builder(getContext())
102 | .setBarcodeFormats(Barcode.QR_CODE)
103 | .build();
104 | mBarcodeDetector.setProcessor(mAppIdProcessor);
105 |
106 | mCameraBuilder = new CameraSource.Builder(getContext(), mBarcodeDetector)
107 | .setFacing(CameraSource.CAMERA_FACING_BACK)
108 | .setAutoFocusEnabled(true)
109 | .setRequestedFps(30.0f);
110 |
111 | mSurfaceView = new SurfaceView(getContext());
112 | mSurfaceView.getHolder().addCallback(new SurfaceCallback());
113 | addView(mSurfaceView);
114 | }
115 |
116 | public AppIdScanner setAppIdCallback(AppIdCallback appIdCallback) {
117 | mAppIdCallback = appIdCallback;
118 | return this;
119 | }
120 |
121 | public void start() {
122 | mStartRequested = true;
123 | startIfReady();
124 | }
125 |
126 | public void stop() {
127 | if (mCameraSource != null) mCameraSource.stop();
128 | mSurfaceView.setVisibility(GONE);
129 | }
130 |
131 | public void release() {
132 | if (mCameraSource != null) mCameraSource.release();
133 | mBarcodeDetector.release();
134 | mAppIdProcessor.release();
135 | }
136 |
137 | private void startIfReady() {
138 | if (!mStartRequested || !mSurfaceAvailable || mCameraSource == null) return;
139 | if (ActivityCompat.checkSelfPermission(getContext(), android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
140 | if (Log.isLoggable(Log.ERROR)) {
141 | Log.e("Required permission `" + android.Manifest.permission.CAMERA + "` not granted.");
142 | }
143 | return;
144 | }
145 | try {
146 | mCameraSource.start(mSurfaceView.getHolder());
147 | mStartRequested = false;
148 | } catch (IOException e) {
149 | if (Log.isLoggable(Log.ERROR)) {
150 | Log.e(e.getMessage(), e);
151 | }
152 | }
153 | }
154 |
155 | private class SurfaceCallback implements SurfaceHolder.Callback {
156 | @Override
157 | public void surfaceCreated(SurfaceHolder surface) {
158 | mSurfaceAvailable = true;
159 | startIfReady();
160 | }
161 |
162 | @Override
163 | public void surfaceDestroyed(SurfaceHolder surface) {
164 | mSurfaceAvailable = false;
165 | }
166 |
167 | @Override
168 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
169 | }
170 | }
171 |
172 | @SuppressWarnings("DrawAllocation")
173 | @Override
174 | protected void onLayout(boolean isChange, int left, int top, int right, int bottom) {
175 | if (!isChange) return;
176 | boolean isPortrait = getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
177 | int parentWidth = right - left;
178 | int parentHeight = bottom - top;
179 | mSurfaceView.layout(0, 0, 1, 1);
180 |
181 | int requestWidth = isPortrait ? parentHeight : parentWidth;
182 | int requestHeight = isPortrait ? parentWidth : parentHeight;
183 |
184 | // Request camera preview
185 | if (mCameraSource != null) {
186 | mCameraSource.stop();
187 | mCameraSource.release();
188 | }
189 | if (Log.isLoggable(Log.VERBOSE)) {
190 | Log.v("Requesting camera preview: " + requestWidth + "x" + requestHeight);
191 | }
192 | mCameraSource = mCameraBuilder.setRequestedPreviewSize(requestWidth, requestHeight).build();
193 | startIfReady();
194 |
195 | post(new Runnable() {
196 | @Override
197 | public void run() {
198 | double parentWidth = getWidth();
199 | double parentHeight = getHeight();
200 |
201 | Size previewSize = mCameraSource.getPreviewSize();
202 | while (previewSize == null) {
203 | previewSize = mCameraSource.getPreviewSize();
204 | try {
205 | TimeUnit.MILLISECONDS.sleep(15);
206 | } catch (InterruptedException e) {
207 | // OK
208 | }
209 | }
210 | if (Log.isLoggable(Log.VERBOSE)) {
211 | Log.v("Actual camera preview is: " + previewSize.getWidth() + "x" + previewSize.getHeight());
212 | }
213 |
214 | boolean isPortrait = getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
215 | double previewWidth = isPortrait ? previewSize.getHeight() : previewSize.getWidth();
216 | double previewHeight = isPortrait ? previewSize.getWidth() : previewSize.getHeight();
217 |
218 | double widthRatio = previewWidth / parentWidth;
219 | double heightRatio = previewHeight / parentHeight;
220 | double surfaceWidth;
221 | double surfaceHeight;
222 | if (heightRatio < widthRatio) {
223 | surfaceWidth = parentHeight * previewWidth / previewHeight;
224 | surfaceHeight = parentHeight;
225 | } else {
226 | surfaceWidth = parentWidth;
227 | surfaceHeight = parentWidth * previewHeight / previewWidth;
228 | }
229 |
230 | double centerLeft = (parentWidth - surfaceWidth) / 2.0;
231 | double centerTop = (parentHeight - surfaceHeight) / 2.0;
232 | mSurfaceView.layout((int) Math.round(centerLeft), (int) Math.round(centerTop), (int) Math.round(surfaceWidth + centerLeft), (int) Math.round(surfaceHeight + centerTop));
233 | if (Log.isLoggable(Log.VERBOSE)) {
234 | Log.v("Resized preview layout to: " + (isPortrait ? mSurfaceView.getHeight() : mSurfaceView.getWidth()) + "x" + (isPortrait ? mSurfaceView.getWidth() : mSurfaceView.getHeight()));
235 | }
236 | }
237 | });
238 | }
239 |
240 | public interface AppIdCallback {
241 | void onLayerAppIdScanned(AppIdScanner scanner, String layerAppId);
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/app/src/providerdemo/java/com/layer/messenger/flavor/DemoAtlasIdScannerActivity.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.flavor;
2 |
3 | import android.Manifest;
4 | import android.app.Dialog;
5 | import android.content.Intent;
6 | import android.content.pm.PackageManager;
7 | import android.os.Bundle;
8 | import android.support.v4.app.ActivityCompat;
9 | import android.support.v7.app.AppCompatActivity;
10 | import android.view.ViewGroup;
11 | import android.widget.FrameLayout;
12 |
13 | import com.google.android.gms.common.GoogleApiAvailability;
14 | import com.layer.messenger.R;
15 | import com.layer.messenger.util.Log;
16 |
17 | import java.util.concurrent.atomic.AtomicBoolean;
18 |
19 | public class DemoAtlasIdScannerActivity extends AppCompatActivity {
20 | private static final String PERMISSION = Manifest.permission.CAMERA;
21 | public static final int PERMISSION_REQUEST_CODE = 21;
22 |
23 | AppIdScanner mAppIdScanner;
24 | private final AtomicBoolean mFoundAppId = new AtomicBoolean(false);
25 |
26 | public void onCreate(Bundle bundle) {
27 | super.onCreate(bundle);
28 | setContentView(R.layout.activity_app_id_scanner);
29 | setTitle(R.string.title_app_id_scanner);
30 | }
31 |
32 | @Override
33 | protected void onResume() {
34 | super.onResume();
35 | if (hasPermission()) {
36 | startScanner();
37 | } else {
38 | requestPermission();
39 | }
40 | }
41 |
42 | @Override
43 | protected void onPause() {
44 | super.onPause();
45 | if (hasPermission()) getAppIdScanner().stop();
46 | }
47 |
48 | @Override
49 | protected void onDestroy() {
50 | super.onDestroy();
51 | if (hasPermission()) getAppIdScanner().release();
52 | }
53 |
54 | private boolean hasPermission() {
55 | return ActivityCompat.checkSelfPermission(this, PERMISSION) == PackageManager.PERMISSION_GRANTED;
56 | }
57 |
58 | /**
59 | * Dynamically add AppIdScanner to layout because dynamic permissions seem to break when added
60 | * ahead of time (onRequestPermissionsResult is never called).
61 | */
62 | private AppIdScanner getAppIdScanner() {
63 | if (mAppIdScanner == null) {
64 | AppIdScanner scanner = new AppIdScanner(this);
65 | FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
66 | scanner.setAppIdCallback(new AppIdScanner.AppIdCallback() {
67 | @Override
68 | public void onLayerAppIdScanned(AppIdScanner scanner, String layerAppId) {
69 | if (!mFoundAppId.compareAndSet(false, true)) return;
70 | if (Log.isLoggable(Log.VERBOSE)) {
71 | Log.v("Found App ID: " + layerAppId);
72 | }
73 | Flavor.setLayerAppId(layerAppId);
74 | Intent intent = new Intent(DemoAtlasIdScannerActivity.this, DemoLoginActivity.class);
75 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
76 | startActivity(intent);
77 | if (!isFinishing()) finish();
78 | }
79 | });
80 | ((FrameLayout) findViewById(R.id.app_id_scanner_layout)).addView(scanner, 0, layoutParams);
81 | mAppIdScanner = scanner;
82 | }
83 | return mAppIdScanner;
84 | }
85 |
86 | private void requestPermission() {
87 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Requesting camera permission.");
88 | ActivityCompat.requestPermissions(this, new String[]{PERMISSION}, PERMISSION_REQUEST_CODE);
89 | }
90 |
91 | @Override
92 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
93 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Got permission result for: " + requestCode);
94 | if (grantResults.length == 0 || requestCode != PERMISSION_REQUEST_CODE) {
95 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
96 | return;
97 | }
98 |
99 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
100 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Camera permission granted.");
101 | startScanner();
102 | } else {
103 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Camera permission denied.");
104 | }
105 | }
106 |
107 | private void startScanner() {
108 | // Check for Google Play
109 | Dialog errorDialog = GoogleApiAvailability.getInstance().getErrorDialog(this, GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this), 1);
110 | if (errorDialog != null) {
111 | errorDialog.show();
112 | } else {
113 | getAppIdScanner().start();
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/app/src/providerdemo/java/com/layer/messenger/flavor/DemoAuthenticationProvider.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.flavor;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.SharedPreferences;
7 | import android.net.Uri;
8 |
9 | import com.layer.messenger.ResumeActivity;
10 | import com.layer.messenger.util.AuthenticationProvider;
11 | import com.layer.messenger.util.Log;
12 | import com.layer.sdk.LayerClient;
13 | import com.layer.sdk.exceptions.LayerException;
14 |
15 | import org.json.JSONObject;
16 |
17 | import java.io.BufferedInputStream;
18 | import java.io.InputStream;
19 | import java.io.OutputStream;
20 | import java.net.HttpURLConnection;
21 | import java.net.URL;
22 |
23 | import static com.layer.messenger.util.Util.streamToString;
24 |
25 | public class DemoAuthenticationProvider implements AuthenticationProvider {
26 | private final SharedPreferences mPreferences;
27 | private Callback mCallback;
28 |
29 | public DemoAuthenticationProvider(Context context) {
30 | mPreferences = context.getSharedPreferences(DemoAuthenticationProvider.class.getSimpleName(), Context.MODE_PRIVATE);
31 | }
32 |
33 | @Override
34 | public AuthenticationProvider setCredentials(Credentials credentials) {
35 | if (credentials == null) {
36 | mPreferences.edit().clear().commit();
37 | return this;
38 | }
39 | mPreferences.edit()
40 | .putString("appId", credentials.getLayerAppId())
41 | .putString("name", credentials.getUserName())
42 | .commit();
43 | return this;
44 | }
45 |
46 | @Override
47 | public boolean hasCredentials() {
48 | return mPreferences.contains("appId");
49 | }
50 |
51 | @Override
52 | public AuthenticationProvider setCallback(Callback callback) {
53 | mCallback = callback;
54 | return this;
55 | }
56 |
57 | @Override
58 | public void onAuthenticated(LayerClient layerClient, String userId) {
59 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Authenticated with Layer, user ID: " + userId);
60 | layerClient.connect();
61 | if (mCallback != null) {
62 | mCallback.onSuccess(this, userId);
63 | }
64 | }
65 |
66 | @Override
67 | public void onDeauthenticated(LayerClient layerClient) {
68 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Deauthenticated with Layer");
69 | }
70 |
71 | @Override
72 | public void onAuthenticationChallenge(LayerClient layerClient, String nonce) {
73 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Received challenge: " + nonce);
74 | respondToChallenge(layerClient, nonce);
75 | }
76 |
77 | @Override
78 | public void onAuthenticationError(LayerClient layerClient, LayerException e) {
79 | String error = "Failed to authenticate with Layer: " + e.getMessage();
80 | if (Log.isLoggable(Log.ERROR)) Log.e(error, e);
81 | if (mCallback != null) {
82 | mCallback.onError(this, error);
83 | }
84 | }
85 |
86 | @Override
87 | public boolean routeLogin(LayerClient layerClient, String layerAppId, Activity from) {
88 | if (layerAppId == null) {
89 | // No App ID: must scan from QR code.
90 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Routing to QR Code scanning Activity");
91 | Intent intent = new Intent(from, DemoAtlasIdScannerActivity.class);
92 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
93 | from.startActivity(intent);
94 | return true;
95 | }
96 |
97 | if ((layerClient != null) && layerClient.isAuthenticated()) {
98 | // The LayerClient is authenticated: no action required.
99 | if (Log.isLoggable(Log.VERBOSE)) Log.v("No authentication routing required");
100 | return false;
101 | }
102 |
103 | if ((layerClient != null) && hasCredentials()) {
104 | // With a LayerClient and cached provider credentials, we can resume.
105 | if (Log.isLoggable(Log.VERBOSE)) {
106 | Log.v("Routing to resume Activity using cached credentials");
107 | }
108 | Intent intent = new Intent(from, ResumeActivity.class);
109 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
110 | intent.putExtra(ResumeActivity.EXTRA_LOGGED_IN_ACTIVITY_CLASS_NAME, from.getClass().getName());
111 | intent.putExtra(ResumeActivity.EXTRA_LOGGED_OUT_ACTIVITY_CLASS_NAME, DemoLoginActivity.class.getName());
112 | from.startActivity(intent);
113 | return true;
114 | }
115 |
116 | // We have a Layer App ID but no cached provider credentials: routing to Login required.
117 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Routing to login Activity");
118 | Intent intent = new Intent(from, DemoLoginActivity.class);
119 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
120 | from.startActivity(intent);
121 | return true;
122 | }
123 |
124 | private void respondToChallenge(LayerClient layerClient, String nonce) {
125 | Credentials credentials = new Credentials(mPreferences.getString("appId", null), mPreferences.getString("name", null));
126 | if (credentials.getUserName() == null || credentials.getLayerAppId() == null) {
127 | if (Log.isLoggable(Log.WARN)) {
128 | Log.w("No stored credentials to respond to challenge with");
129 | }
130 | return;
131 | }
132 |
133 | try {
134 | // Post request
135 | String url = "https://layer-identity-provider.herokuapp.com/apps/" + credentials.getLayerAppId() + "/atlas_identities";
136 | HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
137 | connection.setDoInput(true);
138 | connection.setDoOutput(true);
139 | connection.setRequestMethod("POST");
140 | connection.setRequestProperty("Content-Type", "application/json");
141 | connection.setRequestProperty("Accept", "application/json");
142 | connection.setRequestProperty("X_LAYER_APP_ID", credentials.getLayerAppId());
143 |
144 | // Credentials
145 | JSONObject rootObject = new JSONObject()
146 | .put("nonce", nonce)
147 | .put("name", credentials.getUserName());
148 |
149 | connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
150 |
151 | OutputStream os = connection.getOutputStream();
152 | os.write(rootObject.toString().getBytes("UTF-8"));
153 | os.close();
154 |
155 | // Handle failure
156 | int statusCode = connection.getResponseCode();
157 | if (statusCode != HttpURLConnection.HTTP_OK && statusCode != HttpURLConnection.HTTP_CREATED) {
158 | String error = String.format("Got status %d when requesting authentication for '%s' with nonce '%s' from '%s'",
159 | statusCode, credentials.getUserName(), nonce, url);
160 | if (Log.isLoggable(Log.ERROR)) Log.e(error);
161 | if (mCallback != null) mCallback.onError(this, error);
162 | return;
163 | }
164 |
165 | // Parse response
166 | InputStream in = new BufferedInputStream(connection.getInputStream());
167 | String result = streamToString(in);
168 | in.close();
169 | connection.disconnect();
170 | JSONObject json = new JSONObject(result);
171 | if (json.has("error")) {
172 | String error = json.getString("error");
173 | if (Log.isLoggable(Log.ERROR)) Log.e(error);
174 | if (mCallback != null) mCallback.onError(this, error);
175 | return;
176 | }
177 |
178 | // Answer authentication challenge.
179 | String identityToken = json.optString("identity_token", null);
180 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Got identity token: " + identityToken);
181 | layerClient.answerAuthenticationChallenge(identityToken);
182 | } catch (Exception e) {
183 | String error = "Error when authenticating with provider: " + e.getMessage();
184 | if (Log.isLoggable(Log.ERROR)) Log.e(error, e);
185 | if (mCallback != null) mCallback.onError(this, error);
186 | }
187 | }
188 |
189 | public static class Credentials {
190 | private final String mLayerAppId;
191 | private final String mUserName;
192 |
193 | public Credentials(Uri layerAppId, String userName) {
194 | this(layerAppId == null ? null : layerAppId.getLastPathSegment(), userName);
195 | }
196 |
197 | public Credentials(String layerAppId, String userName) {
198 | mLayerAppId = layerAppId == null ? null : (layerAppId.contains("/") ? layerAppId.substring(layerAppId.lastIndexOf("/") + 1) : layerAppId);
199 | mUserName = userName;
200 | }
201 |
202 | public String getUserName() {
203 | return mUserName;
204 | }
205 |
206 | public String getLayerAppId() {
207 | return mLayerAppId;
208 | }
209 | }
210 | }
211 |
212 |
--------------------------------------------------------------------------------
/app/src/providerdemo/java/com/layer/messenger/flavor/DemoLoginActivity.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.flavor;
2 |
3 | import android.app.ProgressDialog;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.v7.app.ActionBar;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.view.KeyEvent;
9 | import android.view.inputmethod.EditorInfo;
10 | import android.widget.EditText;
11 | import android.widget.TextView;
12 | import android.widget.Toast;
13 |
14 | import com.layer.messenger.App;
15 | import com.layer.messenger.ConversationsListActivity;
16 | import com.layer.messenger.R;
17 | import com.layer.messenger.util.AuthenticationProvider;
18 | import com.layer.messenger.util.Log;
19 |
20 | public class DemoLoginActivity extends AppCompatActivity {
21 | EditText mName;
22 |
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | setContentView(R.layout.activity_login_demo);
27 | final ActionBar actionBar = getSupportActionBar();
28 | if (actionBar != null) actionBar.hide();
29 |
30 | mName = (EditText) findViewById(R.id.name);
31 | mName.setImeOptions(EditorInfo.IME_ACTION_DONE);
32 | mName.setOnEditorActionListener(new TextView.OnEditorActionListener() {
33 | @Override
34 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
35 | if (actionId == EditorInfo.IME_ACTION_DONE || (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
36 | final String name = mName.getText().toString().trim();
37 | if (name.isEmpty()) return true;
38 | login(name);
39 | return true;
40 | }
41 | return false;
42 | }
43 | });
44 | }
45 |
46 | @Override
47 | protected void onResume() {
48 | super.onResume();
49 | mName.setEnabled(true);
50 | }
51 |
52 | private void login(final String name) {
53 | mName.setEnabled(false);
54 | final ProgressDialog progressDialog = new ProgressDialog(this);
55 | progressDialog.setMessage(getResources().getString(R.string.login_dialog_message));
56 | progressDialog.show();
57 | App.authenticate(new DemoAuthenticationProvider.Credentials(App.getLayerAppId(), name),
58 | new AuthenticationProvider.Callback() {
59 | @Override
60 | public void onSuccess(AuthenticationProvider provider, String userId) {
61 | progressDialog.dismiss();
62 | if (Log.isLoggable(Log.VERBOSE)) {
63 | Log.v("Successfully authenticated as `" + name + "` with userId `" + userId + "`");
64 | }
65 | Intent intent = new Intent(DemoLoginActivity.this, ConversationsListActivity.class);
66 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
67 | DemoLoginActivity.this.startActivity(intent);
68 | }
69 |
70 | @Override
71 | public void onError(AuthenticationProvider provider, final String error) {
72 | progressDialog.dismiss();
73 | if (Log.isLoggable(Log.ERROR)) {
74 | Log.e("Failed to authenticate as `" + name + "`: " + error);
75 | }
76 | runOnUiThread(new Runnable() {
77 | @Override
78 | public void run() {
79 | Toast.makeText(DemoLoginActivity.this, error, Toast.LENGTH_LONG).show();
80 | mName.setEnabled(true);
81 | }
82 | });
83 | }
84 | });
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/providerdemo/java/com/layer/messenger/flavor/DemoParticipant.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.flavor;
2 |
3 | import android.net.Uri;
4 |
5 | import com.layer.atlas.provider.Participant;
6 |
7 | public class DemoParticipant implements Participant {
8 | private String mId;
9 | private String mName;
10 | private Uri mAvatarUrl;
11 |
12 | @Override
13 | public String getId() {
14 | return mId;
15 | }
16 |
17 | public void setId(String id) {
18 | mId = id;
19 | }
20 |
21 | @Override
22 | public String getName() {
23 | return mName;
24 | }
25 |
26 | public void setName(String name) {
27 | mName = name;
28 | }
29 |
30 | @Override
31 | public Uri getAvatarUrl() {
32 | return mAvatarUrl;
33 | }
34 |
35 | public void setAvatarUrl(Uri avatarUrl) {
36 | mAvatarUrl = avatarUrl;
37 | }
38 |
39 | @Override
40 | public int compareTo(Participant another) {
41 | return getName().toLowerCase().compareTo(another.getName().toUpperCase());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/providerdemo/java/com/layer/messenger/flavor/DemoParticipantProvider.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.flavor;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 | import android.os.AsyncTask;
6 |
7 | import com.layer.atlas.provider.Participant;
8 | import com.layer.atlas.provider.ParticipantProvider;
9 | import com.layer.messenger.util.Log;
10 |
11 | import org.json.JSONArray;
12 | import org.json.JSONException;
13 | import org.json.JSONObject;
14 |
15 | import java.io.BufferedInputStream;
16 | import java.io.InputStream;
17 | import java.net.HttpURLConnection;
18 | import java.net.URL;
19 | import java.util.ArrayList;
20 | import java.util.Collection;
21 | import java.util.HashMap;
22 | import java.util.List;
23 | import java.util.Map;
24 | import java.util.Queue;
25 | import java.util.concurrent.ConcurrentLinkedQueue;
26 | import java.util.concurrent.atomic.AtomicBoolean;
27 |
28 | import static com.layer.messenger.util.Util.streamToString;
29 |
30 | public class DemoParticipantProvider implements ParticipantProvider {
31 | private final Context mContext;
32 | private String mLayerAppIdLastPathSegment;
33 | private final Queue mParticipantListeners = new ConcurrentLinkedQueue<>();
34 | private final Map mParticipantMap = new HashMap<>();
35 | private final AtomicBoolean mFetching = new AtomicBoolean(false);
36 |
37 | public DemoParticipantProvider(Context context) {
38 | mContext = context.getApplicationContext();
39 | }
40 |
41 | public DemoParticipantProvider setLayerAppId(String layerAppId) {
42 | if (layerAppId.contains("/")) {
43 | mLayerAppIdLastPathSegment = Uri.parse(layerAppId).getLastPathSegment();
44 | } else {
45 | mLayerAppIdLastPathSegment = layerAppId;
46 | }
47 | load();
48 | fetchParticipants();
49 | return this;
50 | }
51 |
52 |
53 | //==============================================================================================
54 | // Atlas ParticipantProvider
55 | //==============================================================================================
56 |
57 | @Override
58 | public Map getMatchingParticipants(String filter, Map result) {
59 | if (result == null) {
60 | result = new HashMap();
61 | }
62 |
63 | synchronized (mParticipantMap) {
64 | // With no filter, return all Participants
65 | if (filter == null) {
66 | result.putAll(mParticipantMap);
67 | return result;
68 | }
69 |
70 | // Filter participants by substring matching first- and last- names
71 | for (DemoParticipant p : mParticipantMap.values()) {
72 | boolean matches = false;
73 | if (p.getName() != null && p.getName().toLowerCase().contains(filter))
74 | matches = true;
75 | if (matches) {
76 | result.put(p.getId(), p);
77 | } else {
78 | result.remove(p.getId());
79 | }
80 | }
81 | return result;
82 | }
83 | }
84 |
85 | @Override
86 | public Participant getParticipant(String userId) {
87 | synchronized (mParticipantMap) {
88 | DemoParticipant participant = mParticipantMap.get(userId);
89 | if (participant != null) return participant;
90 | fetchParticipants();
91 | return null;
92 | }
93 | }
94 |
95 | /**
96 | * Adds the provided Participants to this ParticipantProvider, saves the participants, and
97 | * returns the list of added participant IDs.
98 | */
99 | private DemoParticipantProvider setParticipants(Collection participants) {
100 | List newParticipantIds = new ArrayList<>(participants.size());
101 | synchronized (mParticipantMap) {
102 | for (DemoParticipant participant : participants) {
103 | String participantId = participant.getId();
104 | if (!mParticipantMap.containsKey(participantId))
105 | newParticipantIds.add(participantId);
106 | mParticipantMap.put(participantId, participant);
107 | }
108 | save();
109 | }
110 | alertParticipantsUpdated(newParticipantIds);
111 | return this;
112 | }
113 |
114 |
115 | //==============================================================================================
116 | // Persistence
117 | //==============================================================================================
118 |
119 | /**
120 | * Loads additional participants from SharedPreferences
121 | */
122 | private boolean load() {
123 | synchronized (mParticipantMap) {
124 | String jsonString = mContext.getSharedPreferences("participants", Context.MODE_PRIVATE).getString("json", null);
125 | if (jsonString == null) return false;
126 |
127 | try {
128 | for (DemoParticipant participant : participantsFromJson(new JSONArray(jsonString))) {
129 | mParticipantMap.put(participant.getId(), participant);
130 | }
131 | return true;
132 | } catch (JSONException e) {
133 | if (Log.isLoggable(Log.ERROR)) Log.e(e.getMessage(), e);
134 | }
135 | return false;
136 | }
137 | }
138 |
139 | /**
140 | * Saves the current map of participants to SharedPreferences
141 | */
142 | private boolean save() {
143 | synchronized (mParticipantMap) {
144 | try {
145 | mContext.getSharedPreferences("participants", Context.MODE_PRIVATE).edit()
146 | .putString("json", participantsToJson(mParticipantMap.values()).toString())
147 | .commit();
148 | return true;
149 | } catch (JSONException e) {
150 | if (Log.isLoggable(Log.ERROR)) Log.e(e.getMessage(), e);
151 | }
152 | }
153 | return false;
154 | }
155 |
156 |
157 | //==============================================================================================
158 | // Network operations
159 | //==============================================================================================
160 | private DemoParticipantProvider fetchParticipants() {
161 | if (!mFetching.compareAndSet(false, true)) return this;
162 | new AsyncTask() {
163 | protected Void doInBackground(Void... params) {
164 | try {
165 | // Post request
166 | String url = "https://layer-identity-provider.herokuapp.com/apps/" + mLayerAppIdLastPathSegment + "/atlas_identities";
167 | HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
168 | connection.setDoInput(true);
169 | connection.setDoOutput(false);
170 | connection.setRequestMethod("GET");
171 | connection.addRequestProperty("Content-Type", "application/json");
172 | connection.addRequestProperty("Accept", "application/json");
173 | connection.addRequestProperty("X_LAYER_APP_ID", mLayerAppIdLastPathSegment);
174 |
175 | // Handle failure
176 | int statusCode = connection.getResponseCode();
177 | if (statusCode != HttpURLConnection.HTTP_OK && statusCode != HttpURLConnection.HTTP_CREATED) {
178 | if (Log.isLoggable(Log.ERROR)) {
179 | Log.e(String.format("Got status %d when fetching participants", statusCode));
180 | }
181 | return null;
182 | }
183 |
184 | // Parse response
185 | InputStream in = new BufferedInputStream(connection.getInputStream());
186 | String result = streamToString(in);
187 | in.close();
188 | connection.disconnect();
189 | JSONArray json = new JSONArray(result);
190 | setParticipants(participantsFromJson(json));
191 | } catch (Exception e) {
192 | if (Log.isLoggable(Log.ERROR)) Log.e(e.getMessage(), e);
193 | } finally {
194 | mFetching.set(false);
195 | }
196 | return null;
197 | }
198 | }.execute();
199 | return this;
200 | }
201 |
202 |
203 | //==============================================================================================
204 | // Utils
205 | //==============================================================================================
206 |
207 | private static List participantsFromJson(JSONArray participantArray) throws JSONException {
208 | List participants = new ArrayList<>(participantArray.length());
209 | for (int i = 0; i < participantArray.length(); i++) {
210 | JSONObject participantObject = participantArray.getJSONObject(i);
211 | DemoParticipant participant = new DemoParticipant();
212 | participant.setId(participantObject.optString("id"));
213 | participant.setName(participantObject.optString("name"));
214 | participant.setAvatarUrl(null);
215 | participants.add(participant);
216 | }
217 | return participants;
218 | }
219 |
220 | private static JSONArray participantsToJson(Collection participants) throws JSONException {
221 | JSONArray participantsArray = new JSONArray();
222 | for (DemoParticipant participant : participants) {
223 | JSONObject participantObject = new JSONObject();
224 | participantObject.put("id", participant.getId());
225 | participantObject.put("name", participant.getName());
226 | participantsArray.put(participantObject);
227 | }
228 | return participantsArray;
229 | }
230 |
231 | private DemoParticipantProvider registerParticipantListener(ParticipantListener participantListener) {
232 | if (!mParticipantListeners.contains(participantListener)) {
233 | mParticipantListeners.add(participantListener);
234 | }
235 | return this;
236 | }
237 |
238 | private DemoParticipantProvider unregisterParticipantListener(ParticipantListener participantListener) {
239 | mParticipantListeners.remove(participantListener);
240 | return this;
241 | }
242 |
243 | private void alertParticipantsUpdated(Collection updatedParticipantIds) {
244 | for (ParticipantListener listener : mParticipantListeners) {
245 | listener.onParticipantsUpdated(this, updatedParticipantIds);
246 | }
247 | }
248 |
249 |
250 | //==============================================================================================
251 | // Callbacks
252 | //==============================================================================================
253 |
254 | public interface ParticipantListener {
255 | void onParticipantsUpdated(DemoParticipantProvider provider, Collection updatedParticipantIds);
256 | }
257 | }
--------------------------------------------------------------------------------
/app/src/providerdemo/java/com/layer/messenger/flavor/Flavor.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.flavor;
2 |
3 | import android.content.Context;
4 |
5 | import com.layer.atlas.provider.ParticipantProvider;
6 | import com.layer.messenger.App;
7 | import com.layer.messenger.util.AuthenticationProvider;
8 | import com.layer.messenger.util.Log;
9 | import com.layer.sdk.LayerClient;
10 |
11 | public class Flavor implements App.Flavor {
12 | // Set your Layer App ID from your Layer developer dashboard to bypass the QR-Code scanner.
13 | private final static String LAYER_APP_ID = null;
14 | private final static String GCM_SENDER_ID = "748607264448";
15 |
16 | private String mLayerAppId;
17 |
18 |
19 | //==============================================================================================
20 | // Layer App ID (from LAYER_APP_ID constant or set by QR-Code scanning AppIdScanner Activity
21 | //==============================================================================================
22 |
23 | @Override
24 | public String getLayerAppId() {
25 | // In-memory cached App ID?
26 | if (mLayerAppId != null) {
27 | return mLayerAppId;
28 | }
29 |
30 | // Constant App ID?
31 | if (LAYER_APP_ID != null) {
32 | if (Log.isLoggable(Log.VERBOSE)) {
33 | Log.v("Using constant `App.LAYER_APP_ID` App ID: " + LAYER_APP_ID);
34 | }
35 | mLayerAppId = LAYER_APP_ID;
36 | return mLayerAppId;
37 | }
38 |
39 | // Saved App ID?
40 | String saved = App.getInstance()
41 | .getSharedPreferences("layerAppId", Context.MODE_PRIVATE)
42 | .getString("layerAppId", null);
43 | if (saved == null) return null;
44 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Loaded Layer App ID: " + saved);
45 | mLayerAppId = saved;
46 | return mLayerAppId;
47 | }
48 |
49 | /**
50 | * Sets the current Layer App ID, and saves it for use next time (to bypass QR code scanner).
51 | *
52 | * @param appId Layer App ID to use when generating a LayerClient.
53 | */
54 | protected static void setLayerAppId(String appId) {
55 | appId = appId.trim();
56 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Saving Layer App ID: " + appId);
57 | App.getInstance().getSharedPreferences("layerAppId", Context.MODE_PRIVATE).edit()
58 | .putString("layerAppId", appId).commit();
59 | }
60 |
61 |
62 | //==============================================================================================
63 | // Generators
64 | //==============================================================================================
65 |
66 | @Override
67 | public LayerClient generateLayerClient(Context context, LayerClient.Options options) {
68 | // If no App ID is set yet, return `null`; we'll launch the AppIdScanner to get one.
69 | String appId = getLayerAppId();
70 | if (appId == null) return null;
71 |
72 | options.googleCloudMessagingSenderId(GCM_SENDER_ID);
73 | return LayerClient.newInstance(context, appId, options);
74 | }
75 |
76 | @Override
77 | public ParticipantProvider generateParticipantProvider(Context context, AuthenticationProvider authenticationProvider) {
78 | return new DemoParticipantProvider(context).setLayerAppId(getLayerAppId());
79 | }
80 |
81 | @Override
82 | public AuthenticationProvider generateAuthenticationProvider(Context context) {
83 | return new DemoAuthenticationProvider(context);
84 | }
85 | }
--------------------------------------------------------------------------------
/app/src/providerdemo/res/drawable/app_id_scanner_outline.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/app/src/providerdemo/res/layout/activity_app_id_scanner.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/providerdemo/res/layout/activity_login_demo.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/providerdemo/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #C0FFFFFF
5 |
--------------------------------------------------------------------------------
/app/src/providerdemo/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 32dp
5 | 8dp
6 | 8dp
7 | 8dp
8 |
9 |
--------------------------------------------------------------------------------
/app/src/providerdemo/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Atlas Demo
5 |
6 | Capture Atlas QR Code
7 | Your name
8 | Logging In…
9 |
10 |
--------------------------------------------------------------------------------
/app/src/providerrails/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/providerrails/java/com/layer/messenger/flavor/Flavor.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.flavor;
2 |
3 | import android.content.Context;
4 |
5 | import com.layer.atlas.provider.ParticipantProvider;
6 | import com.layer.messenger.App;
7 | import com.layer.messenger.R;
8 | import com.layer.messenger.flavor.util.CustomEndpoint;
9 | import com.layer.messenger.util.AuthenticationProvider;
10 | import com.layer.messenger.util.Log;
11 | import com.layer.sdk.LayerClient;
12 |
13 | public class Flavor implements App.Flavor {
14 | // Set your Layer App ID from your Layer Developer Dashboard.
15 | public final static String LAYER_APP_ID = null;
16 |
17 | // Set your Google Cloud Messaging Sender ID from your Google Developers Console.
18 | private final static String GCM_SENDER_ID = null;
19 |
20 | @Override
21 | public String getLayerAppId() {
22 | return (LAYER_APP_ID != null) ? LAYER_APP_ID : CustomEndpoint.getLayerAppId();
23 | }
24 |
25 | @Override
26 | public LayerClient generateLayerClient(Context context, LayerClient.Options options) {
27 | String layerAppId = getLayerAppId();
28 | if (layerAppId == null) {
29 | if (Log.isLoggable(Log.ERROR)) Log.e(context.getString(R.string.app_id_required));
30 | return null;
31 | }
32 | if (GCM_SENDER_ID != null) options.googleCloudMessagingSenderId(GCM_SENDER_ID);
33 | CustomEndpoint.setLayerClientOptions(options);
34 | return LayerClient.newInstance(context, layerAppId, options);
35 | }
36 |
37 | @Override
38 | public AuthenticationProvider generateAuthenticationProvider(Context context) {
39 | return new RailsAuthenticationProvider(context);
40 | }
41 |
42 | @Override
43 | public ParticipantProvider generateParticipantProvider(Context context, AuthenticationProvider authenticationProvider) {
44 | return new RailsParticipantProvider(context).setAuthenticationProvider(authenticationProvider);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/providerrails/java/com/layer/messenger/flavor/RailsAuthenticationProvider.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.flavor;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.SharedPreferences;
7 | import android.widget.Toast;
8 |
9 | import com.layer.messenger.R;
10 | import com.layer.messenger.ResumeActivity;
11 | import com.layer.messenger.flavor.util.CustomEndpoint;
12 | import com.layer.messenger.util.AuthenticationProvider;
13 | import com.layer.messenger.util.Log;
14 | import com.layer.sdk.LayerClient;
15 | import com.layer.sdk.exceptions.LayerException;
16 |
17 | import org.json.JSONObject;
18 |
19 | import java.io.BufferedInputStream;
20 | import java.io.InputStream;
21 | import java.io.OutputStream;
22 | import java.net.HttpURLConnection;
23 | import java.net.URL;
24 |
25 | import static com.layer.messenger.util.Util.streamToString;
26 |
27 | public class RailsAuthenticationProvider implements AuthenticationProvider {
28 | private static final String TAG = RailsAuthenticationProvider.class.getSimpleName();
29 |
30 | private final SharedPreferences mPreferences;
31 | private Callback mCallback;
32 |
33 | public RailsAuthenticationProvider(Context context) {
34 | mPreferences = context.getSharedPreferences(TAG, Context.MODE_PRIVATE);
35 | }
36 |
37 | @Override
38 | public AuthenticationProvider setCredentials(Credentials credentials) {
39 | replaceCredentials(credentials);
40 | return this;
41 | }
42 |
43 | @Override
44 | public boolean hasCredentials() {
45 | return getCredentials() != null;
46 | }
47 |
48 | @Override
49 | public AuthenticationProvider setCallback(Callback callback) {
50 | mCallback = callback;
51 | return this;
52 | }
53 |
54 | @Override
55 | public void onAuthenticated(LayerClient layerClient, String userId) {
56 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Authenticated with Layer, user ID: " + userId);
57 | layerClient.connect();
58 | if (mCallback != null) mCallback.onSuccess(this, userId);
59 | }
60 |
61 | @Override
62 | public void onDeauthenticated(LayerClient layerClient) {
63 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Deauthenticated with Layer");
64 | }
65 |
66 | @Override
67 | public void onAuthenticationChallenge(LayerClient layerClient, String nonce) {
68 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Received challenge: " + nonce);
69 | respondToChallenge(layerClient, nonce);
70 | }
71 |
72 | @Override
73 | public void onAuthenticationError(LayerClient layerClient, LayerException e) {
74 | String error = "Failed to authenticate with Layer: " + e.getMessage();
75 | if (Log.isLoggable(Log.ERROR)) Log.e(error, e);
76 | if (mCallback != null) mCallback.onError(this, error);
77 | }
78 |
79 | @Override
80 | public boolean routeLogin(LayerClient layerClient, String layerAppId, Activity from) {
81 | if ((layerClient != null) && layerClient.isAuthenticated()) {
82 | // The LayerClient is authenticated: no action required.
83 | if (Log.isLoggable(Log.VERBOSE)) Log.v("No authentication routing required");
84 | return false;
85 | }
86 |
87 | if (layerAppId == null && !CustomEndpoint.hasEndpoints()) {
88 | // With no Layer App ID (and no CustomEndpoint) we can't authenticate: bail out.
89 | if (Log.isLoggable(Log.ERROR)) Log.v("No Layer App ID set");
90 | Toast.makeText(from, R.string.app_id_required, Toast.LENGTH_LONG).show();
91 | return true;
92 | }
93 |
94 | if ((layerClient != null) && hasCredentials()) {
95 | // With a LayerClient and cached provider credentials, we can resume.
96 | if (Log.isLoggable(Log.VERBOSE)) {
97 | Log.v("Routing to resume Activity using cached credentials");
98 | }
99 | Intent intent = new Intent(from, ResumeActivity.class);
100 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
101 | intent.putExtra(ResumeActivity.EXTRA_LOGGED_IN_ACTIVITY_CLASS_NAME, from.getClass().getName());
102 | intent.putExtra(ResumeActivity.EXTRA_LOGGED_OUT_ACTIVITY_CLASS_NAME, RailsLoginActivity.class.getName());
103 | from.startActivity(intent);
104 | return true;
105 | }
106 |
107 | // We have a Layer App ID but no cached provider credentials: routing to Login required.
108 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Routing to login Activity");
109 | Intent intent = new Intent(from, RailsLoginActivity.class);
110 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
111 | from.startActivity(intent);
112 | return true;
113 | }
114 |
115 | private void replaceCredentials(Credentials credentials) {
116 | if (credentials == null) {
117 | mPreferences.edit().clear().commit();
118 | return;
119 | }
120 | mPreferences.edit()
121 | .putString("appId", credentials.getLayerAppId())
122 | .putString("email", credentials.getEmail())
123 | .putString("password", credentials.getPassword())
124 | .putString("authToken", credentials.getAuthToken())
125 | .commit();
126 | }
127 |
128 | protected Credentials getCredentials() {
129 | if (!mPreferences.contains("appId")) return null;
130 | return new Credentials(
131 | mPreferences.getString("appId", null),
132 | mPreferences.getString("email", null),
133 | mPreferences.getString("password", null),
134 | mPreferences.getString("authToken", null));
135 | }
136 |
137 | private void respondToChallenge(LayerClient layerClient, String nonce) {
138 | Credentials credentials = getCredentials();
139 | if (credentials == null || credentials.getEmail() == null || (credentials.getPassword() == null && credentials.getAuthToken() == null) || credentials.getLayerAppId() == null) {
140 | if (Log.isLoggable(Log.WARN)) {
141 | Log.w("No stored credentials to respond to challenge with");
142 | }
143 | return;
144 | }
145 |
146 | try {
147 | // Post request
148 | String url = "http://layer-identity-provider.herokuapp.com/users/sign_in.json";
149 | HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
150 | connection.setDoInput(true);
151 | connection.setDoOutput(true);
152 | connection.setRequestMethod("POST");
153 | connection.setRequestProperty("Content-Type", "application/json");
154 | connection.setRequestProperty("Accept", "application/json");
155 | connection.setRequestProperty("X_LAYER_APP_ID", credentials.getLayerAppId());
156 | if (credentials.getEmail() != null) {
157 | connection.setRequestProperty("X_AUTH_EMAIL", credentials.getEmail());
158 | }
159 | if (credentials.getAuthToken() != null) {
160 | connection.setRequestProperty("X_AUTH_TOKEN", credentials.getAuthToken());
161 | }
162 |
163 | // Credentials
164 | JSONObject rootObject = new JSONObject();
165 | JSONObject userObject = new JSONObject();
166 | rootObject.put("user", userObject);
167 | userObject.put("email", credentials.getEmail());
168 | userObject.put("password", credentials.getPassword());
169 | rootObject.put("nonce", nonce);
170 |
171 | connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
172 |
173 | OutputStream os = connection.getOutputStream();
174 | os.write(rootObject.toString().getBytes("UTF-8"));
175 | os.close();
176 |
177 | // Handle failure
178 | int statusCode = connection.getResponseCode();
179 | if (statusCode != HttpURLConnection.HTTP_OK && statusCode != HttpURLConnection.HTTP_CREATED) {
180 | String error = String.format("Got status %d when requesting authentication for '%s' with nonce '%s' from '%s'",
181 | statusCode, credentials.getEmail(), nonce, url);
182 | if (Log.isLoggable(Log.ERROR)) Log.e(error);
183 | if (mCallback != null) mCallback.onError(this, error);
184 | return;
185 | }
186 |
187 | // Parse response
188 | InputStream in = new BufferedInputStream(connection.getInputStream());
189 | String result = streamToString(in);
190 | in.close();
191 | connection.disconnect();
192 | JSONObject json = new JSONObject(result);
193 | if (json.has("error")) {
194 | String error = json.getString("error");
195 | if (Log.isLoggable(Log.ERROR)) Log.e(error);
196 | if (mCallback != null) mCallback.onError(this, error);
197 | return;
198 | }
199 |
200 | // Save provider's auth token and remove plain-text password.
201 | String authToken = json.optString("authentication_token", null);
202 | Credentials authedCredentials = new Credentials(credentials.getLayerAppId(), credentials.getEmail(), null, authToken);
203 | replaceCredentials(authedCredentials);
204 |
205 | // Answer authentication challenge.
206 | String identityToken = json.optString("layer_identity_token", null);
207 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Got identity token: " + identityToken);
208 | layerClient.answerAuthenticationChallenge(identityToken);
209 | } catch (Exception e) {
210 | String error = "Error when authenticating with provider: " + e.getMessage();
211 | if (Log.isLoggable(Log.ERROR)) Log.e(error, e);
212 | if (mCallback != null) mCallback.onError(this, error);
213 | }
214 | }
215 |
216 | public static class Credentials {
217 | private final String mLayerAppId;
218 | private final String mEmail;
219 | private final String mPassword;
220 | private final String mAuthToken;
221 |
222 | public Credentials(String layerAppId, String email, String password, String authToken) {
223 | mLayerAppId = layerAppId == null ? null : (layerAppId.contains("/") ? layerAppId.substring(layerAppId.lastIndexOf("/") + 1) : layerAppId);
224 | mEmail = email;
225 | mPassword = password;
226 | mAuthToken = authToken;
227 | }
228 |
229 | public String getEmail() {
230 | return mEmail;
231 | }
232 |
233 | public String getPassword() {
234 | return mPassword;
235 | }
236 |
237 | public String getAuthToken() {
238 | return mAuthToken;
239 | }
240 |
241 | public String getLayerAppId() {
242 | return mLayerAppId;
243 | }
244 | }
245 | }
246 |
247 |
--------------------------------------------------------------------------------
/app/src/providerrails/java/com/layer/messenger/flavor/RailsLoginActivity.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.flavor;
2 |
3 | import android.app.ProgressDialog;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.v7.app.ActionBar;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.view.KeyEvent;
9 | import android.view.ViewGroup;
10 | import android.view.inputmethod.EditorInfo;
11 | import android.widget.EditText;
12 | import android.widget.Spinner;
13 | import android.widget.TextView;
14 | import android.widget.Toast;
15 |
16 | import com.layer.messenger.App;
17 | import com.layer.messenger.ConversationsListActivity;
18 | import com.layer.messenger.R;
19 | import com.layer.messenger.flavor.util.CustomEndpoint;
20 | import com.layer.messenger.util.AuthenticationProvider;
21 | import com.layer.messenger.util.Log;
22 |
23 | public class RailsLoginActivity extends AppCompatActivity {
24 | EditText mEmail;
25 | EditText mPassword;
26 |
27 | @Override
28 | protected void onCreate(Bundle savedInstanceState) {
29 | super.onCreate(savedInstanceState);
30 | setContentView(R.layout.activity_login_rails);
31 | final ActionBar actionBar = getSupportActionBar();
32 | if (actionBar != null) actionBar.hide();
33 |
34 | mEmail = (EditText) findViewById(R.id.email);
35 | mEmail.setImeOptions(EditorInfo.IME_ACTION_NEXT);
36 |
37 | mPassword = (EditText) findViewById(R.id.password);
38 | mPassword.setImeOptions(EditorInfo.IME_ACTION_DONE);
39 | mPassword.setOnEditorActionListener(new TextView.OnEditorActionListener() {
40 | @Override
41 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
42 | if (actionId == EditorInfo.IME_ACTION_DONE || (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
43 | final String email = mEmail.getText().toString().trim();
44 | if (email.isEmpty()) return true;
45 |
46 | final String password = mPassword.getText().toString().trim();
47 | if (password.isEmpty()) return true;
48 |
49 | login(email, password);
50 | return true;
51 | }
52 | return false;
53 | }
54 | });
55 |
56 | // Optionally add a CustomEndpoint Spinner (not typical)
57 | if (Flavor.LAYER_APP_ID == null) {
58 | Spinner customEndpoints = CustomEndpoint.createSpinner(this);
59 | if (customEndpoints != null) {
60 | customEndpoints.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
61 | ((ViewGroup) mPassword.getParent()).addView(customEndpoints);
62 | }
63 | }
64 | }
65 |
66 | @Override
67 | protected void onResume() {
68 | super.onResume();
69 | mEmail.setEnabled(true);
70 | mPassword.setEnabled(true);
71 | }
72 |
73 | private void login(final String email, final String password) {
74 | mEmail.setEnabled(false);
75 | mPassword.setEnabled(false);
76 | final ProgressDialog progressDialog = new ProgressDialog(RailsLoginActivity.this);
77 | progressDialog.setMessage(getResources().getString(R.string.login_dialog_message));
78 | progressDialog.show();
79 | App.authenticate(new RailsAuthenticationProvider.Credentials(App.getLayerAppId(), email, password, null),
80 | new AuthenticationProvider.Callback() {
81 | @Override
82 | public void onSuccess(AuthenticationProvider provider, String userId) {
83 | progressDialog.dismiss();
84 | if (Log.isLoggable(Log.VERBOSE)) {
85 | Log.v("Successfully authenticated as `" + email + "` with userId `" + userId + "`");
86 | }
87 | Intent intent = new Intent(RailsLoginActivity.this, ConversationsListActivity.class);
88 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
89 | RailsLoginActivity.this.startActivity(intent);
90 | }
91 |
92 | @Override
93 | public void onError(AuthenticationProvider provider, final String error) {
94 | progressDialog.dismiss();
95 | if (Log.isLoggable(Log.ERROR)) {
96 | Log.e("Failed to authenticate as `" + email + "`: " + error);
97 | }
98 | runOnUiThread(new Runnable() {
99 | @Override
100 | public void run() {
101 | Toast.makeText(RailsLoginActivity.this, error, Toast.LENGTH_LONG).show();
102 | mEmail.setEnabled(true);
103 | mPassword.setEnabled(true);
104 | }
105 | });
106 | }
107 | });
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/app/src/providerrails/java/com/layer/messenger/flavor/RailsParticipant.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.flavor;
2 |
3 | import android.net.Uri;
4 |
5 | import com.layer.atlas.provider.Participant;
6 |
7 | public class RailsParticipant implements Participant {
8 | private String mId;
9 | private String mFirstName;
10 | private String mLastName;
11 | private String mEmail;
12 | private Uri mAvatarUrl;
13 |
14 | @Override
15 | public String getId() {
16 | return mId;
17 | }
18 |
19 | public void setId(String id) {
20 | mId = id;
21 | }
22 |
23 | public String getFirstName() {
24 | return mFirstName;
25 | }
26 |
27 | public void setFirstName(String firstName) {
28 | mFirstName = firstName;
29 | }
30 |
31 | public String getLastName() {
32 | return mLastName;
33 | }
34 |
35 | public void setLastName(String lastName) {
36 | mLastName = lastName;
37 | }
38 |
39 | public String getEmail() {
40 | return mEmail;
41 | }
42 |
43 | public void setEmail(String email) {
44 | mEmail = email;
45 | }
46 |
47 | @Override
48 | public String getName() {
49 | return (getFirstName() + " " + getLastName()).trim();
50 | }
51 |
52 | @Override
53 | public Uri getAvatarUrl() {
54 | return mAvatarUrl;
55 | }
56 |
57 | public void setAvatarUrl(Uri avatarUrl) {
58 | mAvatarUrl = avatarUrl;
59 | }
60 |
61 | @Override
62 | public int compareTo(Participant another) {
63 | int first = getFirstName().toLowerCase().compareTo(((RailsParticipant) another).getFirstName().toLowerCase());
64 | if (first != 0) return first;
65 | return getLastName().toLowerCase().compareTo(((RailsParticipant) another).getLastName().toLowerCase());
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/providerrails/java/com/layer/messenger/flavor/RailsParticipantProvider.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.flavor;
2 |
3 | import android.content.Context;
4 | import android.os.AsyncTask;
5 |
6 | import com.layer.atlas.provider.Participant;
7 | import com.layer.atlas.provider.ParticipantProvider;
8 | import com.layer.messenger.util.AuthenticationProvider;
9 | import com.layer.messenger.util.Log;
10 |
11 | import org.json.JSONArray;
12 | import org.json.JSONException;
13 | import org.json.JSONObject;
14 |
15 | import java.io.BufferedInputStream;
16 | import java.io.InputStream;
17 | import java.net.HttpURLConnection;
18 | import java.net.URL;
19 | import java.util.ArrayList;
20 | import java.util.Collection;
21 | import java.util.HashMap;
22 | import java.util.List;
23 | import java.util.Map;
24 | import java.util.Queue;
25 | import java.util.concurrent.ConcurrentLinkedQueue;
26 | import java.util.concurrent.atomic.AtomicBoolean;
27 |
28 | import static com.layer.messenger.util.Util.streamToString;
29 |
30 | public class RailsParticipantProvider implements ParticipantProvider {
31 | private final Context mContext;
32 | private final Queue mParticipantListeners = new ConcurrentLinkedQueue<>();
33 | private final Map mParticipantMap = new HashMap();
34 | private final AtomicBoolean mFetching = new AtomicBoolean(false);
35 |
36 | private RailsAuthenticationProvider mAuthenticationProvider;
37 |
38 | public RailsParticipantProvider(Context context) {
39 | mContext = context.getApplicationContext();
40 | load();
41 | fetchParticipants();
42 | }
43 |
44 | public RailsParticipantProvider setAuthenticationProvider(AuthenticationProvider authenticationProvider) {
45 | mAuthenticationProvider = (RailsAuthenticationProvider) authenticationProvider;
46 | fetchParticipants();
47 | return this;
48 | }
49 |
50 | //==============================================================================================
51 | // Atlas ParticipantProvider
52 | //==============================================================================================
53 |
54 | @Override
55 | public Map getMatchingParticipants(String filter, Map result) {
56 | if (result == null) {
57 | result = new HashMap();
58 | }
59 |
60 | synchronized (mParticipantMap) {
61 | // With no filter, return all Participants
62 | if (filter == null) {
63 | result.putAll(mParticipantMap);
64 | return result;
65 | }
66 |
67 | // Filter participants by substring matching first- and last- names
68 | filter = filter.toLowerCase();
69 | for (RailsParticipant p : mParticipantMap.values()) {
70 | boolean matches = false;
71 | if (p.getName() != null && p.getName().toLowerCase().contains(filter))
72 | matches = true;
73 | if (matches) {
74 | result.put(p.getId(), p);
75 | } else {
76 | result.remove(p.getId());
77 | }
78 | }
79 | return result;
80 | }
81 | }
82 |
83 | @Override
84 | public Participant getParticipant(String userId) {
85 | synchronized (mParticipantMap) {
86 | RailsParticipant participant = mParticipantMap.get(userId);
87 | if (participant != null) return participant;
88 | fetchParticipants();
89 | return null;
90 | }
91 | }
92 |
93 | /**
94 | * Adds the provided Participants to this ParticipantProvider, saves the participants, and
95 | * returns the list of added participant IDs.
96 | */
97 | private RailsParticipantProvider setParticipants(Collection participants) {
98 | List newParticipantIds = new ArrayList<>(participants.size());
99 | synchronized (mParticipantMap) {
100 | for (RailsParticipant participant : participants) {
101 | String participantId = participant.getId();
102 | if (!mParticipantMap.containsKey(participantId)) {
103 | newParticipantIds.add(participantId);
104 | }
105 | mParticipantMap.put(participantId, participant);
106 | }
107 | save();
108 | }
109 | alertParticipantsUpdated(newParticipantIds);
110 | return this;
111 | }
112 |
113 |
114 | //==============================================================================================
115 | // Persistence
116 | //==============================================================================================
117 |
118 | /**
119 | * Loads additional participants from SharedPreferences
120 | */
121 | private boolean load() {
122 | synchronized (mParticipantMap) {
123 | String jsonString = mContext.getSharedPreferences("participants", Context.MODE_PRIVATE).getString("json", null);
124 | if (jsonString == null) return false;
125 |
126 | try {
127 | for (RailsParticipant participant : participantsFromJson(new JSONArray(jsonString))) {
128 | mParticipantMap.put(participant.getId(), participant);
129 | }
130 | return true;
131 | } catch (JSONException e) {
132 | if (Log.isLoggable(Log.ERROR)) {
133 | Log.e(e.getMessage(), e);
134 | }
135 | }
136 | return false;
137 | }
138 | }
139 |
140 | /**
141 | * Saves the current map of participants to SharedPreferences
142 | */
143 | private boolean save() {
144 | synchronized (mParticipantMap) {
145 | try {
146 | mContext.getSharedPreferences("participants", Context.MODE_PRIVATE).edit()
147 | .putString("json", participantsToJson(mParticipantMap.values()).toString())
148 | .commit();
149 | return true;
150 | } catch (JSONException e) {
151 | if (Log.isLoggable(Log.ERROR)) {
152 | Log.e(e.getMessage(), e);
153 | }
154 | }
155 | }
156 | return false;
157 | }
158 |
159 |
160 | //==============================================================================================
161 | // Network operations
162 | //==============================================================================================
163 | private RailsParticipantProvider fetchParticipants() {
164 | if (mAuthenticationProvider == null) return this;
165 | RailsAuthenticationProvider.Credentials credentials = mAuthenticationProvider.getCredentials();
166 | if (credentials == null) return this;
167 | if (credentials.getAuthToken() == null) return this;
168 |
169 | if (!mFetching.compareAndSet(false, true)) return this;
170 | new AsyncTask() {
171 | protected Void doInBackground(RailsAuthenticationProvider.Credentials... params) {
172 | try {
173 | // Post request
174 | RailsAuthenticationProvider.Credentials credentials = params[0];
175 | String url = "http://layer-identity-provider.herokuapp.com/users.json";
176 | HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
177 | connection.setDoInput(true);
178 | connection.setDoOutput(false);
179 | connection.setRequestMethod("GET");
180 | connection.addRequestProperty("Content-Type", "application/json");
181 | connection.addRequestProperty("Accept", "application/json");
182 | connection.addRequestProperty("X_LAYER_APP_ID", credentials.getLayerAppId());
183 | if (credentials.getEmail() != null) {
184 | connection.addRequestProperty("X_AUTH_EMAIL", credentials.getEmail());
185 | }
186 | if (credentials.getAuthToken() != null) {
187 | connection.addRequestProperty("X_AUTH_TOKEN", credentials.getAuthToken());
188 | }
189 |
190 | // Handle failure
191 | int statusCode = connection.getResponseCode();
192 | if (statusCode != HttpURLConnection.HTTP_OK && statusCode != HttpURLConnection.HTTP_CREATED) {
193 | if (Log.isLoggable(Log.ERROR)) {
194 | Log.e(String.format("Got status %d when fetching participants", statusCode));
195 | }
196 | return null;
197 | }
198 |
199 | // Parse response
200 | InputStream in = new BufferedInputStream(connection.getInputStream());
201 | String result = streamToString(in);
202 | in.close();
203 | connection.disconnect();
204 | JSONArray json = new JSONArray(result);
205 | setParticipants(participantsFromJson(json));
206 | } catch (Exception e) {
207 | if (Log.isLoggable(Log.ERROR)) Log.e(e.getMessage(), e);
208 | } finally {
209 | mFetching.set(false);
210 | }
211 | return null;
212 | }
213 | }.execute(credentials);
214 | return this;
215 | }
216 |
217 |
218 | //==============================================================================================
219 | // Utils
220 | //==============================================================================================
221 |
222 | private static List participantsFromJson(JSONArray participantArray) throws JSONException {
223 | List participants = new ArrayList<>(participantArray.length());
224 | for (int i = 0; i < participantArray.length(); i++) {
225 | JSONObject participantObject = participantArray.getJSONObject(i);
226 | RailsParticipant participant = new RailsParticipant();
227 | participant.setId(participantObject.optString("id", null));
228 | participant.setFirstName(trimmedValue(participantObject, "first_name", null));
229 | participant.setLastName(trimmedValue(participantObject, "last_name", null));
230 | participant.setEmail(trimmedValue(participantObject, "email", null));
231 | participant.setAvatarUrl(null);
232 | participants.add(participant);
233 | }
234 | return participants;
235 | }
236 |
237 | private static String trimmedValue(JSONObject o, String name, String fallback) {
238 | String s = o.optString(name, fallback);
239 | return (s == null) ? null : s.trim();
240 | }
241 |
242 | private static JSONArray participantsToJson(Collection participants) throws JSONException {
243 | JSONArray participantsArray = new JSONArray();
244 | for (RailsParticipant participant : participants) {
245 | JSONObject participantObject = new JSONObject();
246 | participantObject.put("id", participant.getId());
247 | participantObject.put("first_name", participant.getFirstName());
248 | participantObject.put("last_name", participant.getLastName());
249 | participantObject.put("email", participant.getEmail());
250 | participantsArray.put(participantObject);
251 | }
252 | return participantsArray;
253 | }
254 |
255 | private RailsParticipantProvider registerParticipantListener(ParticipantListener participantListener) {
256 | if (!mParticipantListeners.contains(participantListener)) {
257 | mParticipantListeners.add(participantListener);
258 | }
259 | return this;
260 | }
261 |
262 | private RailsParticipantProvider unregisterParticipantListener(ParticipantListener participantListener) {
263 | mParticipantListeners.remove(participantListener);
264 | return this;
265 | }
266 |
267 | private void alertParticipantsUpdated(Collection updatedParticipantIds) {
268 | for (ParticipantListener listener : mParticipantListeners) {
269 | listener.onParticipantsUpdated(this, updatedParticipantIds);
270 | }
271 | }
272 |
273 |
274 | //==============================================================================================
275 | // Callbacks
276 | //==============================================================================================
277 |
278 | public interface ParticipantListener {
279 | void onParticipantsUpdated(RailsParticipantProvider provider, Collection updatedParticipantIds);
280 | }
281 | }
--------------------------------------------------------------------------------
/app/src/providerrails/java/com/layer/messenger/flavor/util/CustomEndpoint.java:
--------------------------------------------------------------------------------
1 | package com.layer.messenger.flavor.util;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 | import android.widget.AdapterView;
6 | import android.widget.ArrayAdapter;
7 | import android.widget.Spinner;
8 |
9 | import com.layer.messenger.App;
10 | import com.layer.messenger.util.Log;
11 | import com.layer.sdk.LayerClient;
12 |
13 | import org.json.JSONArray;
14 | import org.json.JSONException;
15 | import org.json.JSONObject;
16 |
17 | import java.io.BufferedReader;
18 | import java.io.IOException;
19 | import java.io.InputStream;
20 | import java.io.InputStreamReader;
21 | import java.io.Reader;
22 | import java.io.StringWriter;
23 | import java.io.Writer;
24 | import java.util.ArrayList;
25 | import java.util.Collections;
26 | import java.util.HashMap;
27 | import java.util.List;
28 | import java.util.Map;
29 | import java.util.Set;
30 |
31 | /**
32 | * CustomEndpoint provides a mechanism for using endpoints besides the default Layer endpoints.
33 | * This is only useful for enterprise customers with custom endpoints. Contact support@layer.com
34 | * for information.
35 | *
36 | * @see com.layer.sdk.LayerClient.Options#customEndpoint(String, String, String, String)
37 | */
38 | public class CustomEndpoint {
39 | private static Endpoint sEndpoint;
40 | private static Map sEndpoints;
41 |
42 | public static String getLayerAppId() {
43 | Endpoint endpoint = getEndpoint();
44 | return endpoint == null ? null : endpoint.getAppId();
45 | }
46 |
47 | public static void setLayerClientOptions(LayerClient.Options options) {
48 | Endpoint endpoint = getEndpoint();
49 | if (endpoint != null) endpoint.setLayerClientOptions(options);
50 | }
51 |
52 | public static boolean hasEndpoints() {
53 | Map endpoints = getEndpoints();
54 | return endpoints != null && !endpoints.isEmpty();
55 | }
56 |
57 | public static Spinner createSpinner(Context context) {
58 | Set endpointNames = getNames();
59 | if (endpointNames == null || endpointNames.isEmpty()) return null;
60 |
61 | List namesList = new ArrayList(endpointNames);
62 | Collections.sort(namesList);
63 | ArrayAdapter adapter = new ArrayAdapter(context, android.R.layout.simple_dropdown_item_1line, namesList);
64 | Spinner spinner = new Spinner(context);
65 | spinner.setAdapter(adapter);
66 |
67 | Endpoint endpoint = getEndpoint();
68 | if (endpoint != null) {
69 | int position = namesList.indexOf(endpoint.getName());
70 | if (position != -1) spinner.setSelection(position);
71 | }
72 | setEndpointName((String) spinner.getSelectedItem());
73 |
74 | spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
75 | @Override
76 | public void onItemSelected(AdapterView> parent, View view, int position, long id) {
77 | setEndpointName((String) parent.getSelectedItem());
78 | }
79 |
80 | @Override
81 | public void onNothingSelected(AdapterView> parent) {
82 | setEndpointName(null);
83 | }
84 | });
85 |
86 | return spinner;
87 | }
88 |
89 | private static Set getNames() {
90 | Map endpoints = getEndpoints();
91 | return endpoints == null ? null : endpoints.keySet();
92 | }
93 |
94 | private static void setEndpointName(String name) {
95 | App.getInstance().getSharedPreferences("layer_custom_endpoint", Context.MODE_PRIVATE).edit().putString("name", name).commit();
96 | Map endpoints = getEndpoints();
97 | sEndpoint = (endpoints == null) ? null : endpoints.get(name);
98 | if (Log.isLoggable(Log.VERBOSE)) Log.v("Setting custom endpoint to: " + sEndpoint);
99 | }
100 |
101 | private static Endpoint getEndpoint() {
102 | if (sEndpoint != null) return sEndpoint;
103 | String savedEndpointName = App.getInstance().getSharedPreferences("layer_custom_endpoint", Context.MODE_PRIVATE).getString("name", null);
104 | if (savedEndpointName == null) return null;
105 | Map endpoints = getEndpoints();
106 | sEndpoint = (endpoints == null) ? null : endpoints.get(savedEndpointName);
107 | return sEndpoint;
108 | }
109 |
110 | private static Map getEndpoints() {
111 | if (sEndpoints != null) return sEndpoints;
112 | sEndpoints = new HashMap();
113 |
114 | // Check for endpoints in resources
115 | Context context = App.getInstance();
116 | int resId = context.getResources().getIdentifier("layer_endpoints", "raw", context.getPackageName());
117 | if (resId == 0) return null;
118 |
119 | // Read endpoints from resources
120 | Writer writer = new StringWriter();
121 | char[] buffer = new char[1024];
122 | InputStream is = context.getResources().openRawResource(resId);
123 | try {
124 | Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
125 | int n;
126 | while ((n = reader.read(buffer)) != -1) {
127 | writer.write(buffer, 0, n);
128 | }
129 | } catch (Exception e) {
130 | if (Log.isLoggable(Log.ERROR)) Log.e(e.getMessage(), e);
131 | } finally {
132 | if (is != null) {
133 | try {
134 | is.close();
135 | } catch (IOException e) {
136 | if (Log.isLoggable(Log.ERROR)) Log.e(e.getMessage(), e);
137 | }
138 | }
139 | }
140 | String content = writer.toString().trim();
141 | if (content.isEmpty()) return null;
142 |
143 | // Parse endpoints from JSON
144 | try {
145 | JSONArray array = new JSONArray(content);
146 | for (int i = 0; i < array.length(); i++) {
147 | Endpoint endpoint = new Endpoint(array.getJSONObject(i));
148 | sEndpoints.put(endpoint.getName(), endpoint);
149 | }
150 | return sEndpoints;
151 | } catch (JSONException e) {
152 | if (Log.isLoggable(Log.ERROR)) Log.e(e.getMessage(), e);
153 | }
154 | return null;
155 | }
156 |
157 | public static class Endpoint {
158 | final String mName;
159 | final String mAppId;
160 | final String mGcmSenderId;
161 | final String mProviderUrl;
162 |
163 | final String mPlatformUrl;
164 | final String mPlatformToken;
165 |
166 | final String mEndpointConf;
167 | final String mEndpointCert;
168 | final String mEndpointAuth;
169 | final String mEndpointSync;
170 |
171 | public Endpoint(JSONObject o) throws JSONException {
172 | mName = o.getString("name");
173 | mAppId = o.getString("appId");
174 | mGcmSenderId = o.getString("gcmSenderId");
175 | mProviderUrl = o.getString("providerUrl");
176 |
177 | JSONObject platform = o.optJSONObject("platform");
178 | if (platform != null) {
179 | mPlatformUrl = platform.optString("url");
180 | mPlatformToken = platform.optString("token");
181 | } else {
182 | mPlatformUrl = null;
183 | mPlatformToken = null;
184 | }
185 |
186 | JSONObject endpoint = o.optJSONObject("endpoint");
187 | if (endpoint != null) {
188 | mEndpointConf = endpoint.getString("conf");
189 | mEndpointCert = endpoint.getString("cert");
190 | mEndpointAuth = endpoint.getString("auth");
191 | mEndpointSync = endpoint.getString("sync");
192 | } else {
193 | mEndpointConf = null;
194 | mEndpointCert = null;
195 | mEndpointAuth = null;
196 | mEndpointSync = null;
197 | }
198 | }
199 |
200 | public void setLayerClientOptions(LayerClient.Options options) {
201 | if (mGcmSenderId != null) options.googleCloudMessagingSenderId(mGcmSenderId);
202 |
203 | if (mEndpointAuth != null) {
204 | options.customEndpoint(mEndpointConf, mEndpointCert, mEndpointAuth, mEndpointSync);
205 | }
206 | }
207 |
208 | public String getName() {
209 | return mName;
210 | }
211 |
212 | public String getAppId() {
213 | return mAppId;
214 | }
215 |
216 | @Override
217 | public String toString() {
218 | return "Endpoint{" +
219 | "mName='" + mName + '\'' +
220 | ", mAppId='" + mAppId + '\'' +
221 | ", mGcmSenderId='" + mGcmSenderId + '\'' +
222 | ", mProviderUrl='" + mProviderUrl + '\'' +
223 | ", mPlatformUrl='" + mPlatformUrl + '\'' +
224 | ", mPlatformToken='" + mPlatformToken + '\'' +
225 | ", mEndpointConf='" + mEndpointConf + '\'' +
226 | ", mEndpointCert='" + mEndpointCert + '\'' +
227 | ", mEndpointAuth='" + mEndpointAuth + '\'' +
228 | ", mEndpointSync='" + mEndpointSync + '\'' +
229 | '}';
230 | }
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/app/src/providerrails/res/layout/activity_login_rails.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
16 |
17 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/providerrails/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Atlas Messenger
5 | Please set your Layer App ID in `Flavor.java` when using the Rails Identity Provider. You can get your Layer App ID from the developer dashboard.
6 |
7 | Email
8 | Password
9 | Logging In…
10 |
11 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 | dependencies {
6 | classpath 'com.android.tools.build:gradle:2.1.0'
7 | }
8 | }
9 |
10 | allprojects {
11 | repositories {
12 | maven { url "https://raw.githubusercontent.com/layerhq/releases-android/master/releases/" }
13 | maven { url "https://raw.githubusercontent.com/layerhq/Atlas-Android/master/releases/" }
14 | jcenter()
15 | }
16 | }
17 |
18 | task clean(type: Delete) {
19 | delete rootProject.buildDir
20 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Apr 08 14:36:07 PDT 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
--------------------------------------------------------------------------------