Files
app_Settings/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java

223 lines
7.4 KiB
Java

/*
* Copyright (C) 2022 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 com.android.settings.connecteddevice.stylus;
import android.content.Context;
import android.hardware.BatteryState;
import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.InputDevice;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import java.util.ArrayList;
import java.util.List;
/**
* Controller to maintain available USI stylus devices. Listens to bluetooth
* stylus connection to determine whether to show the USI preference.
*/
public class StylusDeviceUpdater implements InputManager.InputDeviceListener,
InputManager.InputDeviceBatteryListener {
private static final String TAG = "StylusDeviceUpdater";
private static final String PREF_KEY = "stylus_usi_device";
private static final String INPUT_ID_ARG = "device_input_id";
private final DevicePreferenceCallback mDevicePreferenceCallback;
private final List<Integer> mRegisteredBatteryCallbackIds;
private final DashboardFragment mFragment;
private final InputManager mInputManager;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private Context mContext;
@VisibleForTesting
Integer mLastDetectedUsiId;
BatteryState mLastBatteryState;
@VisibleForTesting
Preference mUsiPreference;
public StylusDeviceUpdater(Context context, DashboardFragment fragment,
DevicePreferenceCallback devicePreferenceCallback) {
mFragment = fragment;
mRegisteredBatteryCallbackIds = new ArrayList<>();
mDevicePreferenceCallback = devicePreferenceCallback;
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
mContext = context;
mInputManager = context.getSystemService(InputManager.class);
}
/**
* Register the stylus event callback and update the list
*/
public void registerCallback() {
for (int deviceId : mInputManager.getInputDeviceIds()) {
onInputDeviceAdded(deviceId);
}
mInputManager.registerInputDeviceListener(this, new Handler(Looper.myLooper()));
forceUpdate();
}
/**
* Unregister the stylus event callback
*/
public void unregisterCallback() {
for (int deviceId : mRegisteredBatteryCallbackIds) {
mInputManager.removeInputDeviceBatteryListener(deviceId, this);
}
mInputManager.unregisterInputDeviceListener(this);
}
@Override
public void onInputDeviceAdded(int deviceId) {
InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
if (inputDevice == null) return;
if (inputDevice.supportsSource(InputDevice.SOURCE_STYLUS)
&& !inputDevice.isExternal()) {
try {
mInputManager.addInputDeviceBatteryListener(deviceId,
mContext.getMainExecutor(), this);
mRegisteredBatteryCallbackIds.add(deviceId);
} catch (IllegalArgumentException e) {
Log.e(TAG, e.getMessage());
}
}
forceUpdate();
}
@Override
public void onInputDeviceRemoved(int deviceId) {
Log.d(TAG, String.format("Input device removed %d", deviceId));
forceUpdate();
}
@Override
public void onInputDeviceChanged(int deviceId) {
InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
if (inputDevice == null) return;
if (inputDevice.supportsSource(InputDevice.SOURCE_STYLUS)) {
forceUpdate();
}
}
@Override
public void onBatteryStateChanged(int deviceId, long eventTimeMillis,
@NonNull BatteryState batteryState) {
mLastBatteryState = batteryState;
mLastDetectedUsiId = deviceId;
forceUpdate();
}
/**
* Set the context to generate the {@link Preference}, so it could get the correct theme.
*/
public void setPreferenceContext(Context context) {
mContext = context;
}
/**
* Force update to add or remove stylus preference
*/
public void forceUpdate() {
if (shouldShowUsiPreference()) {
addOrUpdateUsiPreference();
} else {
removeUsiPreference();
}
}
private synchronized void addOrUpdateUsiPreference() {
if (mUsiPreference == null) {
mUsiPreference = new Preference(mContext);
mDevicePreferenceCallback.onDeviceAdded(mUsiPreference);
}
mUsiPreference.setKey(PREF_KEY);
mUsiPreference.setTitle(R.string.stylus_connected_devices_title);
mUsiPreference.setIcon(R.drawable.ic_stylus);
mUsiPreference.setOnPreferenceClickListener((Preference p) -> {
mMetricsFeatureProvider.logClickedPreference(p, mFragment.getMetricsCategory());
launchDeviceDetails();
return true;
});
}
private synchronized void removeUsiPreference() {
if (mUsiPreference != null) {
mDevicePreferenceCallback.onDeviceRemoved(mUsiPreference);
mUsiPreference = null;
}
}
private boolean shouldShowUsiPreference() {
return isUsiBatteryValid() && !hasConnectedBluetoothStylusDevice();
}
@VisibleForTesting
public Preference getPreference() {
return mUsiPreference;
}
@VisibleForTesting
boolean hasConnectedBluetoothStylusDevice() {
for (int deviceId : mInputManager.getInputDeviceIds()) {
InputDevice device = mInputManager.getInputDevice(deviceId);
if (device == null) continue;
if (device.supportsSource(InputDevice.SOURCE_STYLUS)
&& mInputManager.getInputDeviceBluetoothAddress(deviceId) != null) {
return true;
}
}
return false;
}
@VisibleForTesting
boolean isUsiBatteryValid() {
return mLastBatteryState != null
&& mLastBatteryState.isPresent() && mLastBatteryState.getCapacity() > 0f;
}
private void launchDeviceDetails() {
final Bundle args = new Bundle();
args.putInt(INPUT_ID_ARG, mLastDetectedUsiId);
new SubSettingLauncher(mFragment.getContext())
.setDestination(StylusUsiDetailsFragment.class.getName())
.setArguments(args)
.setSourceMetricsCategory(mFragment.getMetricsCategory()).launch();
}
}