1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.connecteddevice.stylus;
18 
19 import android.content.Context;
20 import android.hardware.BatteryState;
21 import android.hardware.input.InputManager;
22 import android.os.Bundle;
23 import android.view.InputDevice;
24 import android.view.View;
25 import android.widget.ImageView;
26 import android.widget.TextView;
27 
28 import androidx.annotation.NonNull;
29 import androidx.preference.Preference;
30 import androidx.preference.PreferenceScreen;
31 
32 import com.android.settings.R;
33 import com.android.settings.core.BasePreferenceController;
34 import com.android.settingslib.core.lifecycle.LifecycleObserver;
35 import com.android.settingslib.core.lifecycle.events.OnCreate;
36 import com.android.settingslib.core.lifecycle.events.OnDestroy;
37 import com.android.settingslib.widget.LayoutPreference;
38 
39 import java.text.NumberFormat;
40 
41 /**
42  * This class adds a header for USI stylus devices with a heading, icon, and battery level.
43  * As opposed to the bluetooth device headers, this USI header gets its battery values
44  * from {@link InputManager} APIs, rather than the bluetooth battery levels.
45  */
46 public class StylusUsiHeaderController extends BasePreferenceController implements
47         InputManager.InputDeviceBatteryListener, LifecycleObserver, OnCreate, OnDestroy {
48 
49     private static final String KEY_STYLUS_USI_HEADER = "stylus_usi_header";
50     private static final String TAG = StylusUsiHeaderController.class.getSimpleName();
51 
52     private final InputManager mInputManager;
53     private final InputDevice mInputDevice;
54 
55     private LayoutPreference mHeaderPreference;
56 
57 
StylusUsiHeaderController(Context context, InputDevice inputDevice)58     public StylusUsiHeaderController(Context context, InputDevice inputDevice) {
59         super(context, KEY_STYLUS_USI_HEADER);
60         mInputDevice = inputDevice;
61         mInputManager = context.getSystemService(InputManager.class);
62     }
63 
64     @Override
displayPreference(PreferenceScreen screen)65     public void displayPreference(PreferenceScreen screen) {
66         mHeaderPreference = screen.findPreference(getPreferenceKey());
67         View view = mHeaderPreference.findViewById(R.id.entity_header);
68         TextView titleView = view.findViewById(R.id.entity_header_title);
69         titleView.setText(R.string.stylus_connected_devices_title);
70 
71         ImageView iconView = mHeaderPreference.findViewById(R.id.entity_header_icon);
72         if (iconView != null) {
73             iconView.setImageResource(R.drawable.ic_stylus);
74             iconView.setContentDescription("Icon for stylus");
75         }
76         refresh();
77         super.displayPreference(screen);
78     }
79 
80     @Override
updateState(Preference preference)81     public void updateState(Preference preference) {
82         refresh();
83     }
84 
refresh()85     private void refresh() {
86         BatteryState batteryState = mInputDevice.getBatteryState();
87         View view = mHeaderPreference.findViewById(R.id.entity_header);
88         TextView summaryView = view.findViewById(R.id.entity_header_summary);
89 
90         if (isValidBatteryState(batteryState)) {
91             summaryView.setVisibility(View.VISIBLE);
92             summaryView.setText(
93                     NumberFormat.getPercentInstance().format(batteryState.getCapacity()));
94         } else {
95             summaryView.setVisibility(View.INVISIBLE);
96         }
97     }
98 
99     /**
100      * This determines if a battery state is 'stale', as indicated by the presence of
101      * battery values.
102      *
103      * A USI battery state is valid (and present) if a USI battery value has been pulled
104      * within the last 1 hour of a stylus touching/hovering on the screen. The header shows
105      * battery values in this case, Conversely, a stale battery state means no USI battery
106      * value has been detected within the last 1 hour. Thus, the USI stylus preference will
107      * not be shown in Settings, and accordingly, the USI battery state won't surface.
108      *
109      * @param batteryState Latest battery state pulled from the kernel
110      */
isValidBatteryState(BatteryState batteryState)111     private boolean isValidBatteryState(BatteryState batteryState) {
112         return batteryState != null
113                 && batteryState.isPresent()
114                 && batteryState.getCapacity() > 0f;
115     }
116 
117     @Override
getAvailabilityStatus()118     public int getAvailabilityStatus() {
119         return AVAILABLE;
120     }
121 
122     @Override
getPreferenceKey()123     public String getPreferenceKey() {
124         return KEY_STYLUS_USI_HEADER;
125     }
126 
127     @Override
onBatteryStateChanged(int deviceId, long eventTimeMillis, @NonNull BatteryState batteryState)128     public void onBatteryStateChanged(int deviceId, long eventTimeMillis,
129             @NonNull BatteryState batteryState) {
130         refresh();
131     }
132 
133     @Override
onCreate(Bundle savedInstanceState)134     public void onCreate(Bundle savedInstanceState) {
135         mInputManager.addInputDeviceBatteryListener(mInputDevice.getId(),
136                 mContext.getMainExecutor(), this);
137     }
138 
139     @Override
onDestroy()140     public void onDestroy() {
141         mInputManager.removeInputDeviceBatteryListener(mInputDevice.getId(),
142                 this);
143     }
144 }
145