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
|
/*
* Copyright (C) 2016 The CyanogenMod 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 lineageos.preference;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
import java.util.List;
import java.util.Objects;
/**
* A RemotePreference is a view into preference logic which lives in another
* process. The primary use case for this is at the platform level where
* many applications may be contributing their preferences into the
* Settings app.
*
* A RemotePreference appears as a PreferenceScreen and redirects to
* the real application when clicked. The remote application can
* send events back to the preference when data changes and the view
* needs to be updated. See RemotePreferenceUpdater for a base class
* to use on the application side which implements the listeners and
* protocol.
*
* The interprocess communication is realized using BroadcastReceivers.
* When the application wants to update the RemotePreference with
* new data, it sends an ACTION_REFRESH_PREFERENCE with a particular
* Uri. The RemotePreference listens while attached, and performs
* an ordered broadcast with ACTION_UPDATE_PREFERENCE back to
* the application, which is then returned to the preference after
* being filled with new data.
*
* The external activity should include the META_REMOTE_RECEIVER
* and (optionally) the META_REMOTE_KEY strings in it's metadata.
* META_REMOTE_RECEIVER must contain the class name of the
* RemotePreferenceUpdater which we should request updates from.
* META_REMOTE_KEY must contain the key used by the preference
* which should match on both sides.
*/
public class RemotePreference extends SelfRemovingPreference
implements RemotePreferenceManager.OnRemoteUpdateListener {
private static final String TAG = RemotePreference.class.getSimpleName();
private static final boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
public static final String ACTION_REFRESH_PREFERENCE =
"lineageos.intent.action.REFRESH_PREFERENCE";
public static final String ACTION_UPDATE_PREFERENCE =
"lineageos.intent.action.UPDATE_PREFERENCE";
public static final String META_REMOTE_RECEIVER =
"org.lineageos.settings.summary.receiver";
public static final String META_REMOTE_KEY =
"org.lineageos.settings.summary.key";
public static final String EXTRA_ENABLED = ":lineage:pref_enabled";
public static final String EXTRA_KEY = ":lineage:pref_key";
public static final String EXTRA_SUMMARY = ":lineage:pref_summary";
protected final Context mContext;
public RemotePreference(Context context, AttributeSet attrs,
int defStyle, int defStyleRes) {
super(context, attrs, defStyle, defStyleRes);
mContext = context;
}
public RemotePreference(Context context, AttributeSet attrs, int defStyle) {
this(context, attrs, defStyle, 0);
}
public RemotePreference(Context context, AttributeSet attrs) {
this(context, attrs, ConstraintsHelper.getAttr(context,
androidx.preference.R.attr.preferenceScreenStyle,
android.R.attr.preferenceScreenStyle));
}
@Override
public void onRemoteUpdated(Bundle bundle) {
if (DEBUG) Log.d(TAG, "onRemoteUpdated: " + bundle.toString());
if (bundle.containsKey(EXTRA_ENABLED)) {
boolean available = bundle.getBoolean(EXTRA_ENABLED, true);
if (available != isAvailable()) {
setAvailable(available);
}
}
if (isAvailable()) {
setSummary(bundle.getString(EXTRA_SUMMARY));
}
}
@Override
public void onAttached() {
super.onAttached();
if (isAvailable()) {
RemotePreferenceManager.get(mContext).attach(getKey(), this);
}
}
@Override
public void onDetached() {
super.onDetached();
RemotePreferenceManager.get(mContext).detach(getKey(), this);
}
protected String getRemoteKey(Bundle metaData) {
String remoteKey = metaData.getString(META_REMOTE_KEY);
return (remoteKey == null || !remoteKey.equals(getKey())) ? null : remoteKey;
}
@Override
public Intent getReceiverIntent() {
final Intent i = getIntent();
if (i == null) {
Log.w(TAG, "No target intent specified in preference!");
return null;
}
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(i,
PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA,
UserHandle.myUserId());
if (results.size() == 0) {
Log.w(TAG, "No activity found for: " + Objects.toString(i));
}
for (ResolveInfo resolved : results) {
ActivityInfo info = resolved.activityInfo;
Log.d(TAG, "ResolveInfo " + Objects.toString(resolved));
Bundle meta = info.metaData;
if (meta == null || !meta.containsKey(META_REMOTE_RECEIVER)) {
continue;
}
String receiverClass = meta.getString(META_REMOTE_RECEIVER);
String receiverPackage = info.packageName;
String remoteKey = getRemoteKey(meta);
if (DEBUG) Log.d(TAG, "getReceiverIntent class=" + receiverClass +
" package=" + receiverPackage + " key=" + remoteKey);
if (remoteKey == null) {
continue;
}
Intent ri = new Intent(ACTION_UPDATE_PREFERENCE);
ri.setComponent(new ComponentName(receiverPackage, receiverClass));
ri.putExtra(EXTRA_KEY, remoteKey);
return ri;
}
return null;
}
}
|