1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
|
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.companion;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import java.util.Objects;
/**
* A service that receives calls from the system when the associated companion device appears
* nearby or is connected, as well as when the device is no longer "present" or connected.
* See {@link #onDeviceAppeared(AssociationInfo)}/{@link #onDeviceDisappeared(AssociationInfo)}.
*
* <p>
* Companion applications must create a service that {@code extends}
* {@link CompanionDeviceService}, and declare it in their AndroidManifest.xml with the
* "android.permission.BIND_COMPANION_DEVICE_SERVICE" permission
* (see {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE}),
* as well as add an intent filter for the "android.companion.CompanionDeviceService" action
* (see {@link #SERVICE_INTERFACE}).
*
* <p>
* Following is an example of such declaration:
* <pre>{@code
* <service
* android:name=".CompanionService"
* android:label="@string/service_name"
* android:exported="true"
* android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE">
* <intent-filter>
* <action android:name="android.companion.CompanionDeviceService" />
* </intent-filter>
* </service>
* }</pre>
*
* <p>
* If the companion application has requested observing device presence (see
* {@link CompanionDeviceManager#startObservingDevicePresence(String)}) the system will
* <a href="https://developer.android.com/guide/components/bound-services"> bind the service</a>
* when it detects the device nearby (for BLE devices) or when the device is connected
* (for Bluetooth devices).
*
* <p>
* The system binding {@link CompanionDeviceService} elevates the priority of the process that
* the service is running in, and thus may prevent
* <a href="https://developer.android.com/topic/performance/memory-management#low-memory_killer">
* the Low-memory killer</a> from killing the process at expense of other processes with lower
* priority.
*
* <p>
* It is possible for an application to declare multiple {@link CompanionDeviceService}-s.
* In such case, the system will bind all declared services, but will deliver
* {@link #onDeviceAppeared(AssociationInfo)} and {@link #onDeviceDisappeared(AssociationInfo)}
* only to one "primary" services.
* Applications that declare multiple {@link CompanionDeviceService}-s should indicate the "primary"
* service using "android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE" service level
* property.
* <pre>{@code
* <property
* android:name="android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE"
* android:value="true" />
* }</pre>
*
* <p>
* If the application declares multiple {@link CompanionDeviceService}-s, but does not indicate
* the "primary" one, the system will pick one of the declared services to use as "primary".
*
* <p>
* If the application declares multiple "primary" {@link CompanionDeviceService}-s, the system
* will pick single one of them to use as "primary".
*/
public abstract class CompanionDeviceService extends Service {
private static final String LOG_TAG = "CompanionDeviceService";
/**
* An intent action for a service to be bound whenever this app's companion device(s)
* are nearby.
*
* <p>The app will be kept alive for as long as the device is nearby or companion app reports
* appeared.
* If the app is not running at the time device gets connected, the app will be woken up.</p>
*
* <p>Shortly after the device goes out of range or the companion app reports disappeared,
* the service will be unbound, and the app will be eligible for cleanup, unless any other
* user-visible components are running.</p>
*
* If running in background is not essential for the devices that this app can manage,
* app should avoid declaring this service.</p>
*
* <p>The service must also require permission
* {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE}</p>
*/
public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
private final Stub mRemote = new Stub();
/**
* Called by system whenever a device associated with this app is available.
*
* @param address the MAC address of the device
* @deprecated please override {@link #onDeviceAppeared(AssociationInfo)} instead.
*/
@Deprecated
@MainThread
public void onDeviceAppeared(@NonNull String address) {
// Do nothing. Companion apps can override this function.
}
/**
* Called by system whenever a device associated with this app stops being available.
*
* Usually this means the device goes out of range or is turned off.
*
* @param address the MAC address of the device
* @deprecated please override {@link #onDeviceDisappeared(AssociationInfo)} instead.
*/
@Deprecated
@MainThread
public void onDeviceDisappeared(@NonNull String address) {
// Do nothing. Companion apps can override this function.
}
/**
* Called by system whenever the system dispatches a message to the app to send it to
* an associated device.
*
* @param messageId system assigned id of the message to be sent
* @param associationId association id of the associated device
* @param message message to be sent
*
* @hide
*/
@MainThread
public void onDispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
// do nothing. Companion apps can override this function for system to send messages.
}
/**
* App calls this method when there's a message received from an associated device,
* which needs to be dispatched to system for processing.
*
* <p>Calling app must declare uses-permission
* {@link android.Manifest.permission#DELIVER_COMPANION_MESSAGES}</p>
*
* @param messageId id of the message
* @param associationId id of the associated device
* @param message messaged received from the associated device
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public final void dispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
CompanionDeviceManager companionDeviceManager =
getSystemService(CompanionDeviceManager.class);
companionDeviceManager.dispatchMessage(messageId, associationId, message);
}
/**
* Called by system whenever a device associated with this app is connected.
*
* @param associationInfo A record for the companion device.
*/
@MainThread
public void onDeviceAppeared(@NonNull AssociationInfo associationInfo) {
if (!associationInfo.isSelfManaged()) {
onDeviceAppeared(associationInfo.getDeviceMacAddressAsString());
}
}
/**
* Called by system whenever a device associated with this app is disconnected.
*
* @param associationInfo A record for the companion device.
*/
@MainThread
public void onDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
if (!associationInfo.isSelfManaged()) {
onDeviceDisappeared(associationInfo.getDeviceMacAddressAsString());
}
}
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
if (Objects.equals(intent.getAction(), SERVICE_INTERFACE)) {
onBindCompanionDeviceService(intent);
return mRemote;
}
Log.w(LOG_TAG,
"Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + "): " + intent);
return null;
}
/**
* Used to track the state of Binder connection in CTS tests.
* @hide
*/
@TestApi
public void onBindCompanionDeviceService(@NonNull Intent intent) {
}
private class Stub extends ICompanionDeviceService.Stub {
final Handler mMainHandler = Handler.getMain();
final CompanionDeviceService mService = CompanionDeviceService.this;
@Override
public void onDeviceAppeared(AssociationInfo associationInfo) {
mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceAppeared(associationInfo));
}
@Override
public void onDeviceDisappeared(AssociationInfo associationInfo) {
mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceDisappeared(associationInfo));
}
@Override
public void onDispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
mMainHandler.postAtFrontOfQueue(
() -> mService.onDispatchMessage(messageId, associationId, message));
}
}
}
|