Skip to content

Commit 12510b0

Browse files
Client integration
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
1 parent 6e8260c commit 12510b0

23 files changed

Lines changed: 706 additions & 2 deletions

File tree

.idea/codeStyles/Project.xml

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

library/src/androidTest/java/com/owncloud/android/AbstractIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,8 @@ public static File extractAsset(String fileName, Context context) throws IOExcep
263263

264264
@After
265265
public void after() {
266-
removeOnClient(client);
267-
removeOnClient(client2);
266+
// removeOnClient(client);
267+
// removeOnClient(client2);
268268
}
269269

270270
private void removeOnClient(OwnCloudClient client) {

library/src/androidTest/java/com/owncloud/android/GetCapabilitiesRemoteOperationIT.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.owncloud.android.lib.resources.status.NextcloudVersion;
2424
import com.owncloud.android.lib.resources.status.OCCapability;
2525
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
26+
import com.owncloud.android.lib.resources.status.Type;
2627

2728
import org.junit.Test;
2829

@@ -163,4 +164,17 @@ private void checkCapability(OCCapability capability, String userId) {
163164
assertTrue(capability.isWCFEnabled().isFalse());
164165
}
165166
}
167+
168+
@Test
169+
public void testClientIntegration() {
170+
// get capabilities
171+
RemoteOperationResult result = new GetCapabilitiesRemoteOperation().execute(nextcloudClient);
172+
assertTrue(result.isSuccess());
173+
assertNotNull(result.getSingleData());
174+
175+
OCCapability capability = (OCCapability) result.getSingleData();
176+
177+
assertEquals(5, capability.getClientIntegrationEndpoints(Type.CONTEXT_MENU, "").size());
178+
assertEquals(2, capability.getClientIntegrationEndpoints(Type.CREATE_NEW, "").size());
179+
}
166180
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Nextcloud Android Library
3+
*
4+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-FileCopyrightText: 2025 Tobias Kaminsky <tobias@kaminsky.me>
6+
* SPDX-License-Identifier: MIT
7+
*/
8+
9+
package com.nextcloud.android.lib.resources.clientintegration
10+
11+
import com.google.gson.annotations.SerializedName
12+
13+
data class App(
14+
val version: Double,
15+
@SerializedName("context-menu")
16+
val contextMenu: List<Endpoint>,
17+
@SerializedName("create-new")
18+
val createNew: List<Endpoint>
19+
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.android.lib.resources.clientintegration
9+
10+
import android.os.Parcelable
11+
import kotlinx.parcelize.Parcelize
12+
13+
@Parcelize
14+
data class Button(
15+
val label: String,
16+
val type: String
17+
) : Element,
18+
Parcelable
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.android.lib.resources.clientintegration
9+
10+
import android.os.Parcelable
11+
import kotlinx.parcelize.Parcelize
12+
13+
@Parcelize
14+
data class ClientIntegrationUI(
15+
val version: Double,
16+
val root: Layout
17+
) : Parcelable
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Nextcloud Android Library
3+
*
4+
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
5+
* SPDX-License-Identifier: MIT
6+
*/
7+
8+
package com.nextcloud.android.lib.resources.clientintegration
9+
10+
import android.os.Parcelable
11+
12+
interface Element : Parcelable
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.android.lib.resources.clientintegration
9+
10+
import com.google.gson.Gson
11+
import com.google.gson.JsonDeserializationContext
12+
import com.google.gson.JsonDeserializer
13+
import com.google.gson.JsonElement
14+
import com.google.gson.JsonParseException
15+
import com.google.gson.JsonSerializationContext
16+
import com.google.gson.JsonSerializer
17+
import java.lang.reflect.Type
18+
19+
class ElementTypeAdapter :
20+
JsonSerializer<Element>,
21+
JsonDeserializer<Element> {
22+
override fun serialize(
23+
src: Element,
24+
type: Type,
25+
context: JsonSerializationContext
26+
): JsonElement {
27+
// needs to be a new Gson instance, otherwise we end up in a loop
28+
val element = Gson().toJsonTree(src)
29+
element.asJsonObject.addProperty("element", src.javaClass.name)
30+
31+
return element
32+
}
33+
34+
@Throws(JsonParseException::class, ClassNotFoundException::class, Throwable::class)
35+
override fun deserialize(
36+
json: JsonElement,
37+
type: Type,
38+
context: JsonDeserializationContext
39+
): Element? {
40+
val jsonObject = json.asJsonObject
41+
val typeName = jsonObject.get("element").asString
42+
43+
try {
44+
val cls: Class<out Element> =
45+
when (typeName) {
46+
"Button" ->
47+
Class.forName("com.nextcloud.android.lib.resources.clientintegration.Button")
48+
as Class<out Element>
49+
50+
"Text" ->
51+
Class.forName("com.nextcloud.android.lib.resources.clientintegration.Text")
52+
as Class<out Element>
53+
54+
"Image" ->
55+
Class.forName("com.nextcloud.android.lib.resources.clientintegration.Image")
56+
as Class<out Element>
57+
58+
"URL" ->
59+
Class.forName("com.nextcloud.android.lib.resources.clientintegration.URL")
60+
as Class<out Element>
61+
62+
else -> return null
63+
}
64+
65+
return Gson().fromJson(json, cls)
66+
} catch (e: ClassNotFoundException) {
67+
throw JsonParseException(e)
68+
}
69+
}
70+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Nextcloud Android Library
3+
*
4+
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
5+
* SPDX-License-Identifier: MIT
6+
*/
7+
8+
package com.nextcloud.android.lib.resources.clientintegration
9+
10+
import android.os.Parcelable
11+
import com.google.gson.annotations.SerializedName
12+
import com.owncloud.android.lib.resources.status.Method
13+
import kotlinx.parcelize.Parcelize
14+
15+
@Parcelize
16+
data class Endpoint(
17+
val name: String,
18+
val url: String,
19+
var method: Method?,
20+
@SerializedName("mimetype_filters")
21+
val mimetypeFilter: String?,
22+
val params: Map<String, String>?,
23+
val icon: String?,
24+
val filter: String?
25+
) : Parcelable
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Nextcloud Android Library
3+
*
4+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-FileCopyrightText: 2025 Tobias Kaminsky <tobias@kaminsky.me>
6+
* SPDX-License-Identifier: MIT
7+
*/
8+
package com.nextcloud.android.lib.resources.clientintegration
9+
10+
import com.google.gson.GsonBuilder
11+
import com.google.gson.JsonArray
12+
import com.google.gson.reflect.TypeToken
13+
import com.nextcloud.common.NextcloudClient
14+
import com.nextcloud.operations.GetMethod
15+
import com.owncloud.android.lib.common.operations.RemoteOperationResult
16+
import com.owncloud.android.lib.common.utils.Log_OC
17+
import com.owncloud.android.lib.ocs.ServerResponse
18+
import com.owncloud.android.lib.resources.OCSRemoteOperation
19+
import org.apache.commons.httpclient.HttpStatus
20+
21+
/**
22+
* Get client integration
23+
*/
24+
class GetClientIntegrationJsonOperation(
25+
val url: String
26+
) : OCSRemoteOperation<JsonArray>() {
27+
@Suppress("TooGenericExceptionCaught")
28+
override fun run(client: NextcloudClient): RemoteOperationResult<JsonArray> {
29+
var result: RemoteOperationResult<JsonArray>
30+
var getMethod: GetMethod? = null
31+
try {
32+
getMethod =
33+
GetMethod(
34+
client.baseUri.toString() + url + JSON_FORMAT,
35+
true
36+
)
37+
val status = client.execute(getMethod)
38+
if (status == HttpStatus.SC_OK) {
39+
val terms =
40+
getServerResponse(
41+
getMethod,
42+
object : TypeToken<ServerResponse<JsonArray>>() {}
43+
)?.ocs?.data
44+
45+
val json = parseResult(getMethod.getResponseBodyAsString())
46+
47+
if (terms != null) {
48+
result = RemoteOperationResult(true, getMethod)
49+
result.resultData = terms
50+
} else {
51+
result = RemoteOperationResult(false, getMethod)
52+
}
53+
} else {
54+
result = RemoteOperationResult(false, getMethod)
55+
}
56+
} catch (e: Exception) {
57+
result = RemoteOperationResult(e)
58+
Log_OC.e(
59+
TAG,
60+
"Get client integration failed: " + result.logMessage,
61+
result.exception
62+
)
63+
} finally {
64+
getMethod?.releaseConnection()
65+
}
66+
return result
67+
}
68+
69+
fun parseResult(response: String): ClientIntegrationUI {
70+
val gson =
71+
GsonBuilder()
72+
.registerTypeHierarchyAdapter(Element::class.java, ElementTypeAdapter())
73+
.create()
74+
75+
return gson.fromJson(response, ClientIntegrationUI::class.java)
76+
}
77+
78+
companion object {
79+
private val TAG = GetClientIntegrationJsonOperation::class.java.simpleName
80+
}
81+
}

0 commit comments

Comments
 (0)