小米日历日程默认开启闹钟提醒

小米日历日程默认开启闹钟提醒

使用小米日历同步Outlook邮箱、QQ邮箱的日历数据,闹钟提醒状态无法同步,因为HyperOS(或MIUI)使用了自定义字段的方式判断是否启用闹钟提醒…于是我尝试使用逆向+Xposed的方式将第三方导入的日程数据默认开启闹钟提醒。

小米日历app中代码

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
public static void m5882a1(Context context, long j, String str, String str2) {
String string;
JSONObject m14072e = h70.m14072e(context, j, str);
if (m14072e != null) {
try {
string = m14072e.getString(C4251g.f23798p);
} catch (Exception e) {
r61.m18986d("Cal:D:Utils", "saveEPInfo()", e);
return;
}
} else {
string = null;
}
ContentValues contentValues = new ContentValues();
contentValues.put("event_id", Long.valueOf(j));
contentValues.put("name", str);
contentValues.put(C4251g.f23798p, str2);
if (string == null) {
r61.m18983a("Cal:D:Utils", "saveEPInfo(): insert");
context.getContentResolver().insert(C3565sv.f19116a, contentValues);
} else {
r61.m18983a("Cal:D:Utils", "saveEPInfo(): update");
context.getContentResolver().update(Uri.withAppendedPath(C3565sv.f19116a, String.valueOf(m14072e.optLong("_id"))), contentValues, null, null);
}
}
1
2
3
4
5
public class C3565sv {

/* renamed from: a */
public static final Uri f19116a = CalendarContract.ExtendedProperties.CONTENT_URI.buildUpon().appendQueryParameter("caller_is_miui_calendar", Boolean.toString(true)).build();
}

日历存储(com.android.providers.calendar)代码

1
2
3
4
5
6
7
8
9
10
11
12
// com.android.providers.calendar.CalendarProvider2.insert


@Override // com.android.providers.calendar.SQLiteContentProvider
public Uri insert(Uri uri0, ContentValues contentValues0) {
if(!this.applyingBatch()) {
Integer integer0 = Binder.getCallingUid();
this.mCallingUid.set(integer0);
}

return super.insert(uri0, contentValues0);
}
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
// com.android.providers.calendar.CalendarProvider2.insertInTransactionInner(android.net.Uri, android.content.ContentValues, boolean) : android.net.Uri

private Uri insertInTransactionInner(Uri uri0, ContentValues contentValues0, boolean z) {
Cursor cursor1;
long v9;
long v8;
Uri uri1;
String s4;
String s3;
String s2;
if(Log.isLoggable("CalP:D:CalendarProvider2", 2)) {
Log.v("CalP:D:CalendarProvider2", "insertInTransaction: " + uri0);
}

CalendarSanityChecker.getInstance(this.mContext).checkLastCheckTime();
this.validateUriParameters(uri0.getQueryParameterNames());
boolean z1 = uri0.getBooleanQueryParameter("caller_is_miui_calendar", false);
int v = CalendarProvider2.sUriMatcher.match(uri0);
this.verifyTransactionAllowed(1, uri0, contentValues0, z, z1, v, null, null);
this.mDb = this.mDbHelper.getWritableDatabase();
String s = "sync_inserted";
Cursor cursor0 = null;
switch(v) {
case 1: {
if(!z) {
contentValues0.put("dirty", Integer.valueOf(1));
this.addMutator(contentValues0, "mutators");
}

if(!contentValues0.containsKey("dtstart")) {
if((contentValues0.containsKey("original_sync_id")) && (contentValues0.containsKey("originalInstanceTime")) && 2 == ((int)contentValues0.getAsInteger("eventStatus"))) {
long v1 = (long)contentValues0.getAsLong("originalInstanceTime");
contentValues0.put("dtstart", Long.valueOf(v1));
contentValues0.put("dtend", Long.valueOf(v1));
contentValues0.put("eventTimezone", "UTC");
goto label_23;
}

throw new RuntimeException("DTSTART field missing from event");
}

label_23:
ContentValues contentValues1 = new ContentValues(contentValues0);
if(z) {
this.scrubEventData(contentValues1, null);
}
else {
this.validateEventData(contentValues1);
}

ContentValues contentValues2 = this.updateLastDate(contentValues1);
if(contentValues2 != null) {
Long long0 = contentValues2.getAsLong("calendar_id");
if(long0 != null) {
String s1 = contentValues2.getAsString("eventColor_index");
if(!TextUtils.isEmpty(s1)) {
Account account0 = this.getAccount(long0.longValue());
if(account0 == null) {
s3 = null;
s2 = null;
}
else {
s2 = account0.name;
s3 = account0.type;
}

contentValues2.put("eventColor", Integer.valueOf(this.verifyColorExists(s2, s3, s1, 1)));
}

if(contentValues2.containsKey("organizer")) {
s4 = null;
}
else {
s4 = this.getOwner(long0.longValue());
if(s4 != null) {
contentValues2.put("organizer", s4);
}
}

if((contentValues2.containsKey("original_sync_id")) && !contentValues2.containsKey("original_id")) {
long v2 = this.getOriginalId(contentValues2.getAsString("original_sync_id"), contentValues2.getAsString("calendar_id"));
if(v2 != -1L) {
contentValues2.put("original_id", Long.valueOf(v2));
}
}
else if(!contentValues2.containsKey("original_sync_id") && (contentValues2.containsKey("original_id"))) {
String s5 = this.getOriginalSyncId(contentValues2.getAsLong("original_id").longValue());
if(!TextUtils.isEmpty(s5)) {
contentValues2.put("original_sync_id", s5);
}
}

if((this.fixAllDayTime(contentValues2, contentValues2)) && (Log.isLoggable("CalP:D:CalendarProvider2", 5))) {
Log.w("CalP:D:CalendarProvider2", "insertInTransaction: allDay is true but sec, min, hour were not 0.");
}

contentValues2.remove("hasAlarm");
long v3 = this.mDbHelper.eventsInsert(contentValues2);
if(v3 != -1L) {
this.updateEventRawTimesLocked(v3, contentValues2);
this.mInstancesHelper.updateInstancesLocked(contentValues2, v3, true, this.mDb);
if(contentValues0.containsKey("selfAttendeeStatus")) {
uri1 = uri0;
if(!TextUtils.equals(uri1.getQueryParameter("account_type"), "com.xiaomi")) {
int v4 = (int)contentValues0.getAsInteger("selfAttendeeStatus");
if(s4 == null) {
s4 = this.getOwner(long0.longValue());
}

this.createAttendeeEntry(v3, v4, s4);
}
}
else {
uri1 = uri0;
}

this.backfillExceptionOriginalIds(v3, contentValues0);
this.sendUpdateNotification(v3, z);
if(!z) {
s = "user_inserted";
}

CalendarProviderLog.logInsertEvent(v3, contentValues0, s, this.getCallingPackageName());
return v3 >= 0L ? ContentUris.withAppendedId(uri1, v3) : null;
}

return null;
}

throw new IllegalArgumentException("New events must specify a calendar id");
}

throw new RuntimeException("Could not insert event.");
}
case 4: {
Integer integer0 = contentValues0.getAsInteger("sync_events");
if(integer0 != null && ((int)integer0) == 1) {
Account account1 = new Account(contentValues0.getAsString("account_name"), contentValues0.getAsString("account_type"));
if(this.isXiaomiAccount(account1)) {
return null;
}

String s6 = contentValues0.getAsString("cal_sync1");
this.mDbHelper.scheduleSync(account1, false, s6);
}

String s7 = contentValues0.getAsString("calendar_color_index");
if(!TextUtils.isEmpty(s7)) {
contentValues0.put("calendar_color", Integer.valueOf(this.verifyColorExists(contentValues0.getAsString("account_name"), contentValues0.getAsString("account_type"), s7, 0)));
}

long v5 = this.mDbHelper.calendarsInsert(contentValues0);
this.sendUpdateNotification(v5, z);
if(!z) {
s = "user_inserted";
}

CalendarProviderLog.logInsertCalendar(v5, contentValues0, s, this.getCachedCallingPackage());
return v5 >= 0L ? ContentUris.withAppendedId(uri0, v5) : null;
}
case 6: {
if(contentValues0.containsKey("event_id")) {
if(!this.doesEventExist(contentValues0.getAsLong("event_id").longValue())) {
Log.i("CalP:D:CalendarProvider2", "Trying to insert a attendee to a non-existent event");
return null;
}

if(!z) {
Long long1 = contentValues0.getAsLong("event_id");
this.mDbHelper.duplicateEvent(long1.longValue());
this.setEventDirty(long1.longValue());
}

long v6 = this.mDbHelper.attendeesInsert(contentValues0);
this.updateEventAttendeeStatus(this.mDb, contentValues0);
return v6 >= 0L ? ContentUris.withAppendedId(uri0, v6) : null;
}

throw new IllegalArgumentException("Attendees values must contain an event_id");
}
case 8: {
Long long2 = contentValues0.getAsLong("event_id");
if(long2 != null) {
if(!this.doesEventExist(long2.longValue())) {
Log.i("CalP:D:CalendarProvider2", "Trying to insert a reminder to a non-existent event");
return null;
}

if(!z) {
this.mDbHelper.duplicateEvent(long2.longValue());
this.setEventDirty(long2.longValue());
}

long v7 = this.mDbHelper.remindersInsert(contentValues0);
this.setHasAlarm(long2.longValue(), 1);
if(Log.isLoggable("CalP:D:CalendarProvider2", 3)) {
Log.d("CalP:D:CalendarProvider2", "insertInternal() changing reminder");
}

this.mCalendarAlarm.checkNextAlarm(false);
return v7 >= 0L ? ContentUris.withAppendedId(uri0, v7) : null;
}

throw new IllegalArgumentException("Reminders values must contain a numeric event_id");
}
case 10: {
Long long3 = contentValues0.getAsLong("event_id");
if(long3 != null) {
if(!this.doesEventExist(long3.longValue())) {
Log.i("CalP:D:CalendarProvider2", "Trying to insert extended properties to a non-existent event id = " + long3);
return null;
}

if(!z) {
Long long4 = contentValues0.getAsLong("event_id");
this.mDbHelper.duplicateEvent(long4.longValue());
this.setEventDirty(long4.longValue());
}

v8 = this.mDbHelper.extendedPropertiesInsert(contentValues0);
return v8 >= 0L ? ContentUris.withAppendedId(uri0, v8) : null;
}

throw new IllegalArgumentException("ExtendedProperties values must contain a numeric event_id");
}
case 12: {
Long long5 = contentValues0.getAsLong("event_id");
if(long5 != null) {
if(!this.doesEventExist(long5.longValue())) {
Log.i("CalP:D:CalendarProvider2", "Trying to insert an alert to a non-existent event");
return null;
}

v8 = this.mDbHelper.calendarAlertsInsert(contentValues0);
return v8 >= 0L ? ContentUris.withAppendedId(uri0, v8) : null;
}

throw new IllegalArgumentException("CalendarAlerts values must contain a numeric event_id");
}
case 16: {
v8 = this.mDbHelper.getSyncState().insert(this.mDb, contentValues0);
return v8 >= 0L ? ContentUris.withAppendedId(uri0, v8) : null;
}
case 2:
case 3:
case 9:
case 11:
case 13:
case 15:
case 20:
case 28: {
throw new UnsupportedOperationException("Cannot insert into that URL: " + uri0);
}
case 29: {
v8 = this.handleInsertException(ContentUris.parseId(uri0), contentValues0, z);
return v8 >= 0L ? ContentUris.withAppendedId(uri0, v8) : null;
}
case 0x1F: {
CalendarProvider2.handleEmmaRequest(contentValues0);
return ContentUris.withAppendedId(uri0, 0L);
}
case 0x20: {
break;
}
default: {
throw new IllegalArgumentException("Unknown URL " + uri0);
}
}

String s8 = uri0.getQueryParameter("account_name");
String s9 = uri0.getQueryParameter("account_type");
String s10 = contentValues0.getAsString("color_index");
if(!TextUtils.isEmpty(s8) && !TextUtils.isEmpty(s9)) {
if(!TextUtils.isEmpty(s10)) {
if((contentValues0.containsKey("color_type")) && (contentValues0.containsKey("color"))) {
contentValues0.put("account_name", s8);
contentValues0.put("account_type", s9);
try {
v9 = (long)contentValues0.getAsLong("color_type");
cursor1 = this.getColorByTypeIndex(s8, s9, v9, s10);
}
catch(Throwable throwable0) {
goto label_174;
}

try {
int v10 = cursor1.getCount();
}
catch(Throwable throwable0) {
goto label_172;
}

if(v10 == 0) {
cursor1.close();
v8 = this.mDbHelper.colorsInsert(contentValues0);
return v8 >= 0L ? ContentUris.withAppendedId(uri0, v8) : null;
}

try {
throw new IllegalArgumentException("color type " + v9 + " and index " + s10 + " already exists for account and type provided");
}
catch(Throwable throwable0) {
label_172:
cursor0 = cursor1;
}

label_174:
if(cursor0 != null) {
cursor0.close();
}

throw throwable0;
}

throw new IllegalArgumentException("New colors must contain COLOR_TYPE and COLOR");
}

throw new IllegalArgumentException("COLOR_INDEX must be non empty for " + uri0);
}

throw new IllegalArgumentException("Account name and type must be non empty parameters for " + uri0);
}

其中 case 1是添加日程,case 10是添加闹铃提醒。

使用xposed默认开启闹钟提醒

在添加提醒后(非闹钟),使用xposed反射调用extendedPropertiesInsert实现开启闹钟提醒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
findAndHookMethod("com.android.providers.calendar.CalendarProvider2", lpparam.classLoader, "insertInTransactionInner", Uri.class, ContentValues.class, boolean.class,new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Uri uri = (Uri) param.args[0];
ContentValues contentValues0 = (ContentValues) param.args[1];
if(uri.toString().equals("content://com.android.calendar/reminders")){
Long long4 = contentValues0.getAsLong("event_id");
Object thisObj = param.thisObject;
Object mDbHelper = getObjectField(thisObj, "mDbHelper");
callMethod(mDbHelper, "duplicateEvent", long4.longValue());
callMethod(thisObj, "setEventDirty", long4.longValue());

ContentValues contentValues1 = new ContentValues();
contentValues1.put("event_id", long4.longValue());
contentValues1.put("name", "agenda_info");
contentValues1.put("value", "{\"need_alarm\":true}");
callMethod(mDbHelper, "extendedPropertiesInsert", contentValues1);
}

}

});

参考资料

https://blog.csdn.net/Spy003/article/details/128888882

https://juejin.cn/post/6844904198769754126

Calendar.db的Event表中存在hasAlarm字段标识闹钟提醒,但是可能存在什么问题导致无法使用,因此华为和OPPO没有实现相关功能。

但是小米通过其他方式进行实现,MIUI控制闹钟提示不是按照传统的EVENT表中的HAS_ALARM字段,而是ExtendedPorperties中的value字段,通过{“need_alarm”:true}标识。

因此添加日历提醒功能要同时对Event、Reminders、ExtendedPorperties三者进行操作