-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSwDisplayManager.cs
415 lines (373 loc) · 16.4 KB
/
SwDisplayManager.cs
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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
using Android.App;
using Android.Content;
using Android.Util;
using AndroidX.Core.App;
using Android.Views.InputMethods;
using Com.Lge.Display;
using Android.Provider;
using System;
using Android.Media;
using SoftWing.SwSystem.Messages;
using SoftWing.SwSystem;
using System.Threading.Tasks;
namespace SoftWing
{
[Service(Exported = true, Enabled = true, Name = "com.jodonlucas.softwing.SoftWing.SwDisplayManager")]
public class SwDisplayManager : Service, SwSystem.MessageSubscriber
{
private const String TAG = "SwDisplayManager";
private DisplayManagerHelper lg_display_manager;
private LgSwivelStateCallback swivel_state_cb;
private MessageDispatcher dispatcher;
private static SwDisplayManager instance;
private const String NOTIFICATION_CHANNEL_ID = "SWKeyboard";
private const String NOTIFICATION_GROUP_ID = "com.jodonlucas.softwing.KEYBOARD";
private static String SW_IME_ID = null;
private const String LG_IME_ID = "com.lge.ime/.LgeImeImpl";
private const int NOTIFICATION_ONGOING_ID = 3532;
private const int SHOW_IME_DELAY_MS = 2000;
private static String OPEN_SOUND_PATH;
private static String CLOSE_SOUND_PATH;
private int media_volume = 0;
private AudioFocusRequestClass focus_request = new AudioFocusRequestClass.Builder(AudioFocus.GainTransient).Build();
public static void StartSwDisplayManager()
{
StartSwDisplayManager(Application.Context);
}
public static void StartSwDisplayManager(Context calling_context)
{
Log.Debug(TAG, "StartSwDisplayManager");
if (instance != null)
{
Log.Debug(TAG, "Display manager exists, skipping");
return;
}
var intent = new Intent(calling_context, typeof(SwDisplayManager));
calling_context.StartForegroundService(intent);
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
Log.Debug(TAG, "OnStartCommand");
SetNotificationInternal();
return StartCommandResult.Sticky;
}
public SwDisplayManager()
{
Log.Debug(TAG, "SwDisplayManager");
lg_display_manager = new DisplayManagerHelper(this);
instance = this;
dispatcher = SwSystem.MessageDispatcher.GetInstance();
dispatcher.Subscribe(SwSystem.MessageType.DisplayUpdate, this);
dispatcher.Subscribe(SwSystem.MessageType.ShowIme, this);
dispatcher.Subscribe(SwSystem.MessageType.AudioUpdate, this);
swivel_state_cb = new LgSwivelStateCallback();
lg_display_manager.RegisterSwivelStateCallback(swivel_state_cb);
OPEN_SOUND_PATH = SwSettings.GetOpenSoundPath();
CLOSE_SOUND_PATH = SwSettings.GetCloseSoundPath();
}
~SwDisplayManager()
{
Log.Debug(TAG, "~SwDisplayManager");
}
private void CreateNotificationChannel()
{
var name = "SoftWing";
var description = "SoftWing";
var importance = NotificationImportance.Low;
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance);
channel.Description = description;
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
NotificationManager notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.CreateNotificationChannel(channel);
}
public void SetProfileNotification(int notification_id, string profile)
{
Log.Debug(TAG, "SetProfileNotification: " + profile + " - " + notification_id.ToString());
var notification_receiver = new NotificationReceiver(profile);
var pFilter = new IntentFilter(notification_receiver.ProfileActionString);
RegisterReceiver(notification_receiver, pFilter);
Intent notificationIntent = new Intent(notification_receiver.ProfileActionString);
PendingIntent contentIntent = PendingIntent.GetBroadcast(Application.Context, 1, notificationIntent, PendingIntentFlags.Mutable);
String title = "Show " + profile + " Controller";
var builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.SetSmallIcon(Resource.Mipmap.ic_launcher_foreground)
.SetColor(Resource.Color.accent_material_dark)
.SetAutoCancel(false)
.SetContentTitle(title)
.SetContentIntent(contentIntent)
.SetOngoing(true)
.SetVisibility((int)NotificationVisibility.Public)
.SetPriority(NotificationCompat.PriorityDefault)
.SetGroup(NOTIFICATION_GROUP_ID);
NotificationManager notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.Notify(notification_id, builder.Build());
}
public static void SetNotification()
{
if (instance != null)
{
instance.SetNotificationInternal();
}
else
{
StartSwDisplayManager();
}
}
public void SetNotificationInternal()
{
Log.Debug(TAG, "SetNotificationInternal()");
NotificationManager notificationManager = (NotificationManager)GetSystemService(NotificationService);
if (notificationManager.GetNotificationChannel(NOTIFICATION_CHANNEL_ID) == null)
{
CreateNotificationChannel();
}
// Close existing notifications before refreshing
notificationManager.CancelAll();
Intent notificationIntent = new Intent(NotificationReceiver.ACTION_SHOW);
PendingIntent contentIntent = PendingIntent.GetBroadcast(Application.Context, 1, notificationIntent, PendingIntentFlags.Mutable);
String title = "Show SoftWing Controller";
var notification = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.SetSmallIcon(Resource.Mipmap.ic_launcher_foreground)
.SetColor(Resource.Color.accent_material_dark)
.SetAutoCancel(false)
.SetContentTitle(title)
.SetContentIntent(contentIntent)
.SetOngoing(true)
.SetVisibility((int)NotificationVisibility.Public)
.SetPriority(NotificationCompat.PriorityDefault)
.SetGroup(NOTIFICATION_GROUP_ID)
.SetGroupSummary(true)
.Build();
StartForeground(NOTIFICATION_ONGOING_ID, notification);
var profile_list = SwSettings.GetKeymapList();
for (int i = 0; i < profile_list.Count; i++)
{
SetProfileNotification(NOTIFICATION_ONGOING_ID + i + 1, profile_list[i]);
}
}
private static bool IsUsingSwKeyboard()
{
var current_ime = Settings.Secure.GetString(Application.Context.ContentResolver, Settings.Secure.DefaultInputMethod);
return current_ime.Contains("SoftWingInput");
}
private static void SetInputMethod(string input_method_id)
{
Log.Debug(TAG, "Setting Input Method");
try
{
// Note, to be able to perform this action you need to grant this app secure settings permissions
// With debugging enabled on your device and adb installed on your PC, connect your phone and run the following command:
// adb shell pm grant com.jodonlucas.softwing android.permission.WRITE_SECURE_SETTINGS
Settings.Secure.PutString(Application.Context.ContentResolver, Settings.Secure.DefaultInputMethod, input_method_id);
}
catch (Exception ex)
{
// If we can't write the setting directly, try the old fashioned way.
// Note: This will only work if SoftWing is the current input method.
Log.Debug(TAG, "Failed to write secure setting, using IMM: " + ex.Message);
InputMethodManager imm = (InputMethodManager)
Application.Context.GetSystemService(InputMethodService);
imm.SetInputMethod(SoftWingInput.InputSessionToken, input_method_id);
}
}
private void ShowSwKeyboard()
{
Log.Debug(TAG, "ShowSwKeyboard");
InputMethodManager input_manager = (InputMethodManager)
Application.Context.GetSystemService(InputMethodService);
if (input_manager == null)
{
throw new Exception("Failed to get SoftWingInput service");
}
input_manager.ShowSoftInputFromInputMethod(SoftWingInput.InputSessionToken, ShowFlags.Forced);
Log.Debug(TAG, "Waiting for IME to open...");
var end_time = Java.Lang.JavaSystem.CurrentTimeMillis() + SHOW_IME_DELAY_MS;
while (!SoftWingInput.ImeIsOpen)
{
// Make sure we aren't waiting forever
if (Java.Lang.JavaSystem.CurrentTimeMillis() > end_time)
{
Log.Error(TAG, "IME NEVER OPENED!");
return;
}
}
}
public static void UseLgKeyboard()
{
SetInputMethod(LG_IME_ID);
}
public static void UseSwKeyboard()
{
if (IsUsingSwKeyboard())
{
Log.Debug(TAG, "Already using SW IME, skipping");
return;
}
if (SW_IME_ID == null)
{
InputMethodManager imm = (InputMethodManager)
Application.Context.GetSystemService(InputMethodService);
foreach (var InputMethod in imm.EnabledInputMethodList)
{
Log.Debug(TAG, "InputMethod: " + InputMethod.Id.ToString());
if (InputMethod.Id.Contains("SoftWingInput"))
{
SW_IME_ID = InputMethod.Id;
break;
}
}
}
SetInputMethod(SW_IME_ID);
}
private void StartSound(String audio_path)
{
// Play as a system sound
Ringtone swingRing = RingtoneManager.GetRingtone(ApplicationContext, Android.Net.Uri.Parse(audio_path));
swingRing.AudioAttributes = new AudioAttributes.Builder().SetFlags(AudioFlags.None).SetLegacyStreamType(Android.Media.Stream.System).Build();
swingRing.Play();
}
public void PlayWingSound(String audio_path)
{
Log.Debug(TAG, "PlayWingSound");
if (String.IsNullOrEmpty(audio_path))
{
Log.Debug(TAG, "Audio File Invalid!");
return;
}
try
{
StartSound(audio_path);
}
catch (Exception ex)
{
Log.Debug(TAG, "Failed to play audio: " + ex.Message);
var audio_manager = (AudioManager)GetSystemService(AudioService);
audio_manager.SetStreamVolume(Android.Media.Stream.Music, media_volume, 0);
audio_manager.AbandonAudioFocusRequest(focus_request);
}
}
private void HandleAudioUpdate(AudioUpdateMessage audio_message)
{
switch (audio_message.Type)
{
case AudioUpdateMessage.AudioType.SwingOpen:
OPEN_SOUND_PATH = audio_message.AudioPath.ToString();
break;
case AudioUpdateMessage.AudioType.SwingClose:
CLOSE_SOUND_PATH = audio_message.AudioPath.ToString();
break;
default:
break;
}
}
private void HandleShowIme()
{
// Run in a background task so the notification tray isn't held open
Task.Factory.StartNew(() =>
{
if (!SoftWingInput.ImeIsOpen)
{
UseSwKeyboard();
ShowSwKeyboard();
}
// If the notification tray closes and kills the keyboard, reopen it.
var end_time = Java.Lang.JavaSystem.CurrentTimeMillis() + SHOW_IME_DELAY_MS;
while (Java.Lang.JavaSystem.CurrentTimeMillis() < end_time)
{
if (!SoftWingInput.ImeIsOpen)
{
ShowSwKeyboard();
break;
}
}
// Handle transition to the bottom screen
if (lg_display_manager.SwivelState == DisplayManagerHelper.SwivelSwiveled)
{
UseLgKeyboard();
// Give the LG keyboard time to perform the screen transition
new Android.OS.Handler(Android.OS.Looper.MainLooper).PostDelayed(delegate
{
UseSwKeyboard();
}, SwSettings.GetTransitionDelayMs());
}
});
}
private void HandleDisplayUpdate(DisplayUpdateMessage display_message)
{
switch (display_message.SwivelState)
{
case DisplayManagerHelper.SwivelStart:
Log.Debug(TAG, "DisplayManagerHelper.SwivelStart");
PlayWingSound(OPEN_SOUND_PATH);
break;
case DisplayManagerHelper.SwivelEnd:
Log.Debug(TAG, "DisplayManagerHelper.SwivelEnd");
// We don't want to impose this behavior unless we are displaying the SoftWing IME
if (IsUsingSwKeyboard() && SoftWingInput.ImeIsOpen)
{
HandleShowIme();
}
break;
case DisplayManagerHelper.NonSwivelStart:
Log.Debug(TAG, "DisplayManagerHelper.NonSwivelStart");
PlayWingSound(CLOSE_SOUND_PATH);
break;
default:
break;
}
}
public void Accept(SoftWing.SwSystem.SystemMessage message)
{
Log.Debug(TAG, "Accept");
switch (message.getMessageType())
{
case SwSystem.MessageType.ShowIme:
HandleShowIme();
break;
case SwSystem.MessageType.DisplayUpdate:
HandleDisplayUpdate((DisplayUpdateMessage)message);
break;
case SwSystem.MessageType.AudioUpdate:
HandleAudioUpdate((AudioUpdateMessage)message);
break;
default:
Log.Debug(TAG, "Invalid message type");
break;
}
}
public override Android.OS.IBinder OnBind(Intent intent)
{
Log.Debug(TAG, "OnBind");
return null;
}
}
/**
* Updates the swivel state based on the callback actions.
*
* @param state The state of swivel, e.g. SWIVEL_START, SWIVEL_END, etc.
*/
public class LgSwivelStateCallback : DisplayManagerHelper.SwivelStateCallback
{
private const String TAG = "LgSwivelStateCallback";
private SwSystem.MessageDispatcher dispatcher;
private bool ignore_transition = true;
public LgSwivelStateCallback()
{
dispatcher = SwSystem.MessageDispatcher.GetInstance();
}
public override void OnSwivelStateChanged(int state)
{
Log.Debug(TAG, "OnSwivelStateChanged");
// The callback manager runs once on startup to report the initial state.
// We only want updates if that state changes.
if (ignore_transition)
{
Log.Debug(TAG, "Ignoring first swivel action");
ignore_transition = false;
return;
}
dispatcher.Post(new DisplayUpdateMessage(state));
}
}
}