Add docs for plugin development

This commit is contained in:
MM20 2024-02-08 00:37:20 +01:00
parent c3d8d8a632
commit c492239a82
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
12 changed files with 285 additions and 13 deletions

1
docs/.gitignore vendored
View File

@ -2,3 +2,4 @@ node_modules
dist
.vitepress/cache
.vitepress/build
public/reference

View File

@ -6,6 +6,7 @@
<a href="/docs/user-guide">User Guide</a>
<a href="/docs/developer-guide">Developer Guide</a>
<a href="/docs/contributor-guide">Contributor Guide</a>
<a href="/reference/index.html" target="_blank">SDK Reference</a>
</div>
<div class="column">
<h4>Legal</h4>
@ -14,8 +15,8 @@
</div>
<div class="column">
<h4>Links</h4>
<a href="https://github.com/MM2-0/Kvaesitso">Github</a>
<a href="https://t.me/Kvaesitso">Telegram</a>
<a href="https://github.com/MM2-0/Kvaesitso" target="_blank">Github</a>
<a href="https://t.me/Kvaesitso" target="_blank">Telegram</a>
</div>
</div>
<p class="copyright">
@ -36,10 +37,13 @@
.column {
display: flex;
flex-direction: column;
row-gap: 0.25rem;
h4 {
font-weight: 600;
margin-bottom: 0.5rem;
}
}
gap: 2rem;
}
.copyright {
margin-top: 2rem;

View File

@ -1,6 +0,0 @@
label: Plugins
position: 4
link:
type: generated-index
title: Plugins
description: In this chapter you will learn how external apps can integrate with Kvaesitso.

View File

@ -0,0 +1,7 @@
# Access Control
Plugins potentially deal with sensitive user data. It is important to control, which apps can access a plugin's APIs in order to avoid any data breaches.
For that reason, the plugin SDK has a built-in permission system. When a user first tries to enable a plugin in Kvaesitso, the launcher sends a permission request to the plugin. The plugin then shows a dialog which allows the user to either grant or deny the request. Once the request is granted, the package name of the requesting app is added to an internally stored allowlist. When an app tries to use a plugin, the plugin checks whether the calling app has been allowlisted before. If it hasn't, it throws a `SecurityException`.
All the plugin SDK's plugin base classes already implement this system so there are no additional steps needed and your plugin is protected by default. However, if you wish to add or remove apps to or from the allowlist manually, you can use the <a href="/reference/plugins/sdk/de.mm20.launcher2.sdk.permissions/-plugin-permission-manager/index.html" target="_blank">`PluginPermissionManager`</a> class.

View File

@ -1,5 +1,50 @@
# Get Started
First, create a new project in Android Studio.
# Get started
## Get the plugin SDK
Kvaesitso comes with a plugin SDK that abstracts away the low level details of inter-app communication and streamlines the process of creating plugins.
Add the following dependency to your project:
```
de.mm20.launcher2:plugin-sdk:$version
```
The current version is: [![](https://img.shields.io/maven-central/v/de.mm20.launcher2/plugin-sdk?style=flat-square)](https://central.sonatype.com/artifact/de.mm20.launcher2/plugin-sdk)
## Create your first plugin
Start by creating a new class and giving it a meaningful name.
```kt
class MyFirstPlugin
```
This class needs to extends one of the plugin base classes. Which class to extend depends on the kind of plugin that you want to develop. Please refer to the article of the respective plugin type to continue. But first, regardless of plugin type, you need to register your plugin in the `AndroidManifest.xml`.
Under the hood, plugins are implemented using Android's content provider APIs. While the plugin SDK abstracts most of that away from you, you still need to register the plugin class as a content provider in the `AndroidManifest.xml`:
```xml
<provider
android:name=".MyFirstPlugin"
android:authorities="your.package.name.authority"
android:exported="true">
<intent-filter>
<action android:name="de.mm20.launcher2.action.PLUGIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</provider>
```
- `android:name` is the class name of your plugin class.
- `android:authorities` must be a globally unique name. It is a good practice to prefix it with your app's package name and add a unique suffix.
> [!WARNING]
> You must not change this later or things will break.
- The `<intent-filter />` lets Kvaesitso know that this content provider is a plugin.
## Next steps
Your next steps depend on the type of plugin that you want to develop:
- Weather provider plugin: please refer to

View File

@ -0,0 +1,83 @@
# Metadata
You can customize how your plugin appears in Kvaesitso's UI.
## Plugin package
These properties apply to your whole plugin package. They are shown in the list of plugins and at the top section of the plugin detail page. `<meta-data />` elements must be added as a direct child of the `<application />` element in your `AndroidManifest.xml`.
### Label
```xml
<meta-data
android:name="de.mm20.launcher2.plugin.label"
android:value="@string/plugin_package_name" />
```
The label that is used for the plugin package. If none is set, the application label is used instead.
### Description
```xml
<meta-data
android:name="de.mm20.launcher2.plugin.description"
android:value="@string/plugin_package_description" />
```
A description for your plugin package.
### Author
```xml
<meta-data
android:name="de.mm20.launcher2.plugin.author"
android:value="Your Name" />
```
The name of the plugin author.
### Icon
```xml
<meta-data
android:name="de.mm20.launcher2.plugin.icon"
android:resource="@drawable/ic_plugin_icon" />
```
The icon for the plugin package. If none is set, the app icon is used instead.
## Plugin
These properties apply to a single plugin. One plugin package can contain multiple plugins, but all plugins of one plugin package are grouped on the same settings screen.
Plugin metadata are either set as attributes on the `<content-provider />` element, or as `<meta-data />` elements that are direct children of the `<content-provider />` element in the `AndroidManifest.xml`:
### Label
```xml
<content-provider
android:label="@string/plugin_label" />
```
This is used in several places in the launcher UI, depending on the type of plugin. For example, if your plugin is a weather plugin, this is shown in the weather provider settings. If your plugin is a file search plugin, this is shown on the file search settings screen. If none is set, the application label is used instead.
### Icon
```xml
<content-provider
android:icon="@drawable/ic_plugin" />
```
For search plugins, this is used on the little badge that indicates from which plugin a search result originated. If none is set, the application icon is used instead.
### Description
```xml
<meta-data
android:name="de.mm20.launcher2.plugin.description"
android:value="@string/plugin_description" />
```
A static description what your plugin does. This can be overridden by the plugin itself to display dynamic information instead.

View File

@ -0,0 +1,15 @@
Some plugins need to be configured before they can be used. For example, users might need to connect an account, or provide an API key.
If your plugin has such requirements, you can override
```kt
suspend fun getPluginState(): PluginState
```
This method can either return <a href="/reference/plugins/sdk/de.mm20.launcher2.sdk/-plugin-state/-ready/index.html" target="_blank">`PluginState.Ready`</a>, or <a href="/reference/plugins/sdk/de.mm20.launcher2.sdk/-plugin-state/-setup-required/index.html" target="_blank">`PluginState.SetupRequired`</a>.
- `PluginState.Ready` can have a status `text` to describe what the plugin does in its current configuration. For example _"Search {username}'s files on {service}_". This overrides the plugin's [description](/docs/developer-guide/plugins/metadata.html#description-1).
- `PluginState.SetupRequired` needs to a `setupActivity` Intent that starts the setup. You can also provide a `message` to describe what kind of setup needs to be performed. For example _"Sign in with {service} to search files on {service}"_
> [!IMPORTANT]
> This method is only meant to provide hints to the launcher's user interface. You should not rely on it as a safeguard for your other plugin methods. There is still a chance that your other plugin methods are called regardless of the return value of this method. Make sure to check your requirements in the other plugin methods as well.

View File

@ -0,0 +1,11 @@
# File Search
TODO
## Plugin state
<!--@include: ./common/_plugin_state.md-->
## Examples
- [OneDrive plugin](https://github.com/Kvaesitso/Plugin-OneDrive)

View File

@ -0,0 +1,79 @@
# Weather Provider Plugins
Weather provider plugins need to extend the <a href="/reference/plugins/sdk/de.mm20.launcher2.sdk.weather/-weather-provider/index.html" target="_blank">`WeatherProvider`</a> class:
```kt
class MyWeatherProviderPlugin : WeatherProvider(
WeatherPluginConfig()
)
```
In the super constructor call, pass a <a href="/reference/core/shared/de.mm20.launcher2.plugin.config/-weather-plugin-config/index.html" target="_blank">`WeatherPluginConfig`</a> object.
## Location search
If your weather provider service provides an API to lookup locations, you should override
```kt
suspend fun findLocations(query: String, lang: String): List<WeatherLocation>
```
This method is called when a user has _Auto location_ disabled and they are trying to set a new location.
The default implementation uses the Android Geocoder, but this API has the limitation that it relies on Google Play Services so you should use your own implementation whenever feasable.
`findLocations` returns a list of <a href="/reference/plugins/sdk/de.mm20.launcher2.sdk.weather/-weather-location/index.html" target="_blank">`WeatherLocation`</a>s. Return an empty list if no location has been found.
### Location types
There are two types of locations:
- <a href="/reference/plugins/sdk/de.mm20.launcher2.sdk.weather/-weather-location/-lat-lon/index.html" target="_blank">`WeatherLocation.LatLon`</a>: use this when your weather service identifies locations by their geo coordinates.
- <a href="/reference/plugins/sdk/de.mm20.launcher2.sdk.weather/-weather-location/-id/index.html" target="_blank">`WeatherLocation.Id`</a>: use this when your weather service has an internal ID system to identify locations.
## Featch weather data
Implement both `getWeatherData` methods:
```kt
suspend fun getWeatherData(lat: Double, lon: Double, lang: String?): List<Forecast>?`
```
```kt
suspend fun getWeatherData(location: WeatherLocation, lang: String?): List<Forecast>?
```
The first method is called when the user has _Auto location_ enabled. Are the last known coordinates of the user.
The second method is called when the user has set their location to a fixed location. `location` is guaranteed to be a value that has been returned by `findLocations` before. If you haven't overriden `findLocations`, this will always be a `WeatherLocation.LatLon`.
Both methods return a list of <a href="/reference/plugins/sdk/de.mm20.launcher2.sdk.weather/-forecast/index.html" target="_blank">`Forecast`</a>s. If an error occurs, you can throw an instruction or return `null`, in this case the launcher will keep the old data and start another attempt at a later time.
`Forecast` objects need at least a `timestamp` (unix time in millis), a `temperature`, a `condition`, an `icon`, a `location` name, and a `provider` name.
- The `condition` should preferably be localized in the user's language, which is provided by the `lang` parameter.
- To construct a <a href="/reference/plugins/sdk/de.mm20.launcher2.sdk.weather/-temperature/index.html" target="_blank">`Temperature`</a>, you can use the `Double.C`, `Double.F`, or `Double.K` helper functions, depending on whether the numeric value returned by your weather service API is in degrees celsius, degrees fahrenheit, or kelvin:
```kt
val temp = tempValueInCelcius.C
val temp2 = tempValueInFahrenheit.F
val temp3 = tempValueInKelvin.K
```
Similar helper functions are available to construct
- `Pressure` (`Double.hPa`, and `Double.mbar`), and
- `WindSpeed` values (`Double.m_s`, `Double.km_h`, and `Double.mph`)
- `location` is the name of the location.
- In fixed location mode, you should read this value from the `location` parameter, to ensure that the name in the weather widget matches the name that the user has set in preferences.
- In auto location mode, if your weather service does not give you a location name, you can use the <a href="/reference/plugins/sdk/de.mm20.launcher2.sdk.weather/-weather-provider/get-location-name.html" target="_blank">`getLocationName`</a> method to reverse geocode the location name using Android's Geocoder API.
## Plugin state
<!--@include: ./common/_plugin_state.md-->
## Examples
- [OpenWeatherMap plugin](https://github.com/Kvaesitso/Plugin-OpenWeatherMap)

View File

@ -1 +0,0 @@
# Search provider

View File

@ -53,4 +53,39 @@ export const DeveloperGuideSidebar: DefaultTheme.SidebarItem[] = [
},
],
},
{
text: 'Plugin Development',
items: [
{
text: 'Get Started',
link: '/docs/developer-guide/plugins/get-started',
},
{
text: 'Plugin Types',
items: [
{
text: 'Weather Provider',
link: '/docs/developer-guide/plugins/plugin-types/weather',
},
{
text: 'File Search Provider',
link: '/docs/developer-guide/plugins/plugin-types/file-search',
},
],
},
{
text: 'Metadata',
link: '/docs/developer-guide/plugins/metadata',
},
{
text: 'Access Control',
link: '/docs/developer-guide/plugins/access-control',
},
{
text: 'Reference',
link: '/reference/index.html',
target: '_blank',
},
],
},
]