Skip to content

Commit

Permalink
Merge pull request #70 from Andrewerr/4-use-notify_push
Browse files Browse the repository at this point in the history
WIP: Implement websocket in addition to polling
  • Loading branch information
0xf104a authored Dec 3, 2023
2 parents 7357ec0 + 6bd46c8 commit 7c600e1
Show file tree
Hide file tree
Showing 28 changed files with 759 additions and 209 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation 'com.github.invissvenska:NumberPickerPreference:1.0.3'
implementation "com.github.nextcloud:Android-SingleSignOn:0.6.1"
implementation 'org.java-websocket:Java-WebSocket:1.5.4'

// Image Loading Library:Glide
implementation 'com.github.bumptech.glide:glide:4.15.1'
Expand Down
26 changes: 18 additions & 8 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- Permissions for old android versions -->
<uses-permission android:name="android.permission.INTERNET" /> <!-- Permissions for old android versions -->
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />

Expand All @@ -21,18 +20,28 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.NextcloudServices">
android:theme="@style/Theme.NextcloudServices"
android:usesCleartextTraffic="true">
<service
android:name=".Services.NotificationWebsocketService"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</service>

<activity android:name=".CreditsActivity"
<activity
android:name=".CreditsActivity"
android:parentActivityName=".SettingsActivity" />

<service
android:name=".Services.NotificationService"
android:name=".Services.NotificationPollService"
android:enabled="true"
android:exported="false" />

<receiver android:name=".BootReceiver"
<receiver
android:name=".BootReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
Expand All @@ -44,9 +53,10 @@
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,28 @@

import android.graphics.Bitmap;

import com.polar.nextcloudservices.Services.PollUpdateListener;
import com.polar.nextcloudservices.API.websocket.NotificationWebsocket;
import com.polar.nextcloudservices.API.websocket.INotificationWebsocketEventListener;
import com.polar.nextcloudservices.Services.INotificationListener;
import com.polar.nextcloudservices.Services.Status.StatusCheckable;

import org.json.JSONObject;

import java.io.IOException;

/*
* Nextcloud abstract API crates possibility to use different libraries for
* Nextcloud abstract API creates possibility to use different libraries for
* polling for notifications. This is needed to use Nextcloud SSO library
* since it does not give per-app key.
* The inheritors of this interface should be passed to NotificationService.
*/
public interface NextcloudAbstractAPI extends StatusCheckable {
public interface INextcloudAbstractAPI extends StatusCheckable {
/**
* Gets all notifications from server
* @param service PollUpdateListener which handles notifications
* @return notifications response as a JSONObject
*/
JSONObject getNotifications(PollUpdateListener service);
JSONObject getNotifications(INotificationListener service);

/**
* Removes notification from server
Expand Down Expand Up @@ -67,4 +69,12 @@ public interface NextcloudAbstractAPI extends StatusCheckable {
* @throws Exception in case of any error
*/
boolean checkNewNotifications() throws Exception;

/**
* @return WebsocketClient instance which holds pre-authorized connection
* @throws Exception in case of any unhandlable error
* @doc Gets websocket client which is authorized and receives notification updates
*/
NotificationWebsocket getNotificationsWebsocket(INotificationWebsocketEventListener listener) throws Exception;

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

import androidx.annotation.NonNull;

import com.polar.nextcloudservices.API.websocket.NotificationWebsocket;
import com.polar.nextcloudservices.API.websocket.INotificationWebsocketEventListener;
import com.polar.nextcloudservices.BuildConfig;
import com.polar.nextcloudservices.Services.PollUpdateListener;
import com.polar.nextcloudservices.Services.INotificationListener;
import com.polar.nextcloudservices.Services.Settings.ServiceSettings;
import com.polar.nextcloudservices.Services.Status.Status;

Expand All @@ -21,13 +23,14 @@
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

import javax.net.ssl.HttpsURLConnection;

public class NextcloudHttpAPI implements NextcloudAbstractAPI {
public class NextcloudHttpAPI implements INextcloudAbstractAPI {
private final String TAG = "NextcloudHttpAPI";
private final String UA = "NextcloudServices/" + BuildConfig.VERSION_NAME;
private String mStatusString = "Updating settings";
Expand Down Expand Up @@ -195,8 +198,57 @@ public boolean checkNewNotifications() throws Exception {
return false;
}

@NonNull
private JSONObject readConnectionToJson(@NonNull HttpURLConnection conn)
throws IOException, JSONException{
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder buffer = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
buffer.append(line);
}
in.close();
return new JSONObject(buffer.toString());
}

//See get_endpoint from
// https://github.com/nextcloud/notify_push/blob/main/test_client/src/main.rs
@NonNull
private String getWebsocketURL() throws Exception {
HttpURLConnection connection = request(
"/ocs/v2.php/cloud/capabilities",
"GET", true);
connection.setConnectTimeout(5000);
connection.setDoInput(true);
JSONObject capabilities = readConnectionToJson(connection);
return capabilities.getJSONObject("ocs")
.getJSONObject("data")
.getJSONObject("capabilities")
.getJSONObject("notify_push")
.getJSONObject("endpoints")
.getString("websocket");
}

@Override
public JSONObject getNotifications(PollUpdateListener service) {
public NotificationWebsocket getNotificationsWebsocket(INotificationWebsocketEventListener listener) throws Exception {
Log.i(TAG, "Starting new websocket connection");
String endpoint = "";
try {
endpoint = getWebsocketURL();
} catch (Exception e){
Log.e(TAG, "Can not get websocket URL", e);
return null;
}
NotificationWebsocket client = new NotificationWebsocket(new URI(endpoint),
mServiceSettings.getUsername(),
mServiceSettings.getPassword(),
listener, this);
client.connect();
return client;
}

@Override
public JSONObject getNotifications(INotificationListener service) {
try {
HttpURLConnection conn = request("/ocs/v2.php/apps/notifications/api/v2/notifications",
"GET", true);
Expand All @@ -205,18 +257,11 @@ public JSONObject getNotifications(PollUpdateListener service) {
String responseCode = Integer.toString(conn.getResponseCode());
Log.d(TAG, "--> GET "+ getEndpoint(mServiceSettings) + "/ocs/v2.php/apps/notifications/api/v2/notifications -- " + responseCode);

BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder buffer = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
buffer.append(line);
}
in.close();
//Log.d(TAG, buffer.toString());
JSONObject response = new JSONObject(buffer.toString());
JSONObject response = readConnectionToJson(conn);
lastPollSuccessful = true;

service.onPollFinished(response);
service.onNewNotifications(response);
return response;
} catch (JSONException e) {
Log.e(TAG, "Error parsing JSON");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
import com.nextcloud.android.sso.QueryParam;
import com.nextcloud.android.sso.aidl.NextcloudRequest;
import com.nextcloud.android.sso.api.NextcloudAPI;
import com.nextcloud.android.sso.api.Response;
import com.nextcloud.android.sso.model.SingleSignOnAccount;
import com.polar.nextcloudservices.Services.PollUpdateListener;
import com.polar.nextcloudservices.API.websocket.NotificationWebsocket;
import com.polar.nextcloudservices.API.websocket.INotificationWebsocketEventListener;
import com.polar.nextcloudservices.Services.INotificationListener;
import com.polar.nextcloudservices.Services.Status.Status;

import org.json.JSONException;
Expand All @@ -30,9 +31,10 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class NextcloudSSOAPI implements NextcloudAbstractAPI {
import kotlin.NotImplementedError;

public class NextcloudSSOAPI implements INextcloudAbstractAPI {
final private NextcloudAPI API;
final private static String TAG = "NextcloudSSOAPI";
private boolean lastPollSuccessful = false;
Expand All @@ -56,7 +58,7 @@ public void onError(Exception ex) {
}

@Override
public JSONObject getNotifications(PollUpdateListener service) {
public JSONObject getNotifications(INotificationListener service) {
Log.d(TAG, "getNotifications");
Map<String, List<String>> header = new HashMap<>();
LinkedList<String> values = new LinkedList<>();
Expand Down Expand Up @@ -85,7 +87,7 @@ public JSONObject getNotifications(PollUpdateListener service) {

try {
JSONObject response = new JSONObject(buffer.toString());
service.onPollFinished(response);
service.onNewNotifications(response);
Log.d(TAG, "Setting lastPollSuccessful as true");
lastPollSuccessful = true;
return response;
Expand Down Expand Up @@ -175,6 +177,11 @@ public boolean checkNewNotifications() throws Exception {
return true;
}

@Override
public NotificationWebsocket getNotificationsWebsocket(INotificationWebsocketEventListener listener) throws Exception {
throw new NotImplementedError("getNotificationsWebsoket() is not implemented for SSO API");
}

@Override
public Status getStatus(Context context) {
if(lastPollSuccessful){
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.polar.nextcloudservices.API.websocket;

import com.polar.nextcloudservices.Services.INotificationListener;

public interface INotificationWebsocketEventListener extends INotificationListener {
/**
* Called whenever websocket is disconnected
* @param isError whether disconnect resulted from error
*/
void onWebsocketDisconnected(boolean isError);

/**
* Called whenever websocket connection is established
*/
void onWebsocketConnected();
}
Loading

0 comments on commit 7c600e1

Please sign in to comment.