Screen showing GCM registered apps + infos + unregistration option

This commit is contained in:
fiaxh 2016-08-28 13:01:35 +02:00 committed by Marvin W
parent b33e43c1f6
commit 33a6137aec
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
10 changed files with 443 additions and 15 deletions

View File

@ -0,0 +1,130 @@
/*
* Copyright 2013-2015 microG Project Team
*
* 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 org.microg.gms.gcm;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class GcmData {
private static final String GCM_REGISTRATION_PREF = "gcm_registrations";
private static final String GCM_MESSAGES_PREF = "gcm_messages";
private static final String REMOVED = "%%REMOVED%%";
private static final String ERROR = "%%ERROR%%";
private Context context;
public GcmData(Context context) {
this.context = context;
}
public static class AppInfo implements Comparable<AppInfo> {
public String app = null;
public String appSignature = null;
public String registerID = null;
private final int STATE_ERROR = 1;
private final int STATE_REMOVED = 2;
private final int STATE_REGISTERED = 3;
private int state;
public AppInfo(String key, String value) {
if (ERROR.equals(value)) {
state = STATE_ERROR;
} else if (REMOVED.equals(value)) {
state = STATE_REMOVED;
} else {
state = STATE_REGISTERED;
registerID = value;
}
String[] splitKey = key.split(":");
app = splitKey[0];
appSignature = splitKey[1];
}
public boolean isRegistered() {
return state == STATE_REGISTERED;
}
public boolean isRemoved() {
return state == STATE_REMOVED;
}
public boolean hasUnregistrationError() {
return state == STATE_ERROR;
}
@Override
public int compareTo(AppInfo another) {
return app.compareTo(another.app);
}
}
public void app_registered(String app, String signature, String regId) {
getInfoSharedPreferences().edit().putString(app + ":" + signature, regId).apply();
}
public void app_registration_error(String app, String signature) {
getInfoSharedPreferences().edit().putString(app + ":" + signature, "-").apply();
}
public void app_unregistered(String app, String signature) {
getInfoSharedPreferences().edit().putString(app + ":" + signature, REMOVED).apply();
}
public void app_unregistration_error(String app, String signature) {
getInfoSharedPreferences().edit().putString(app + ":" + signature, ERROR).apply();
}
public void incrementAppMessageCount(String app, int i) {
int messageCount = getStatsSharedPreferences().getInt(app, 0);
getStatsSharedPreferences().edit().putInt(app, messageCount + i).apply();
}
public int getAppMessageCount(String app) {
return getStatsSharedPreferences().getInt(app, 0);
}
public AppInfo getAppInfo(String app, String signature) {
return getAppInfo(app + signature);
}
public List<AppInfo> getAppsInfo() {
ArrayList<AppInfo> ret = new ArrayList<>();
Set<String> keys = getInfoSharedPreferences().getAll().keySet();
for (String key : keys) {
ret.add(getAppInfo(key));
}
return ret;
}
private AppInfo getAppInfo(String key) {
return new AppInfo(key, getInfoSharedPreferences().getString(key, ""));
}
private SharedPreferences getInfoSharedPreferences() {
return context.getSharedPreferences(GCM_REGISTRATION_PREF, Context.MODE_PRIVATE);
}
private SharedPreferences getStatsSharedPreferences() {
return context.getSharedPreferences(GCM_MESSAGES_PREF, Context.MODE_PRIVATE);
}
}

View File

@ -100,6 +100,8 @@ public class McsService extends Service implements Handler.Callback {
private static HandlerThread handlerThread;
private static Handler rootHandler;
private GcmData gcmStorage = new GcmData(this);
private AlarmManager alarmManager;
private PowerManager powerManager;
private static PowerManager.WakeLock wakeLock;
@ -322,6 +324,8 @@ public class McsService extends Service implements Handler.Callback {
}
if (infos.isEmpty())
logd("No target for message, wut?");
gcmStorage.incrementAppMessageCount(msg.category, 1);
}
private void handleSelfMessage(DataMessageStanza msg) {

View File

@ -20,7 +20,6 @@ import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Message;
import android.os.Messenger;
@ -48,20 +47,34 @@ import static org.microg.gms.gcm.GcmConstants.EXTRA_UNREGISTERED;
public class PushRegisterService extends IntentService {
private static final String TAG = "GmsGcmRegisterSvc";
private static final String REMOVED = "%%REMOVED%%";
private static final String ERROR = "%%ERROR%%";
private static final String GCM_REGISTRATION_PREF = "gcm_registrations";
private static final String EXTRA_SKIP_TRY_CHECKIN = "skip_checkin";
private GcmData gcmStorage = new GcmData(this);
public PushRegisterService() {
super(TAG);
setIntentRedelivery(false);
}
private SharedPreferences getSharedPreferences() {
return getSharedPreferences(GCM_REGISTRATION_PREF, MODE_PRIVATE);
public static RegisterResponse register(Context context, String app, String appSignature, String sender, String info) {
RegisterResponse response = register(context, app, appSignature, sender, info, false);
String regId = response.token;
if (regId != null) {
(new GcmData(context)).app_registered(app, appSignature, regId);
} else {
(new GcmData(context)).app_registration_error(app, appSignature);
}
return response;
}
public static RegisterResponse unregister(Context context, String app, String appSignature, String sender, String info) {
RegisterResponse response = register(context, app, appSignature, sender, info, true);
if (!app.equals(response.deleted)) {
(new GcmData(context)).app_unregistration_error(app, appSignature);
} else {
(new GcmData(context)).app_unregistered(app, appSignature);
}
return response;
}
@Override
@ -106,13 +119,11 @@ public class PushRegisterService extends IntentService {
Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION);
String appSignature = PackageUtils.firstSignatureDigest(this, app);
String regId = register(this, app, appSignature, sender, null, false).token;
String regId = register(this, app, appSignature, sender, null).token;
if (regId != null) {
outIntent.putExtra(EXTRA_REGISTRATION_ID, regId);
getSharedPreferences().edit().putString(app + ":" + appSignature, regId).apply();
} else {
outIntent.putExtra(EXTRA_ERROR, ERROR_SERVICE_NOT_AVAILABLE);
getSharedPreferences().edit().putString(app + ":" + appSignature, "-").apply();
}
Log.d(TAG, "register[res]: " + outIntent + " extras=" + outIntent.getExtras());
@ -159,20 +170,18 @@ public class PushRegisterService extends IntentService {
Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION);
String appSignature = PackageUtils.firstSignatureDigest(this, app);
if (REMOVED.equals(getSharedPreferences().getString(app + ":" + appSignature, null))) {
if (!gcmStorage.getAppInfo(app, appSignature).isRemoved()) {
outIntent.putExtra(EXTRA_UNREGISTERED, app);
} else {
RegisterResponse response = register(this, app, appSignature, null, null, true);
RegisterResponse response = unregister(this, app, appSignature, null, null);
if (!app.equals(response.deleted)) {
outIntent.putExtra(EXTRA_ERROR, ERROR_SERVICE_NOT_AVAILABLE);
getSharedPreferences().edit().putString(app + ":" + PackageUtils.firstSignatureDigest(this, app), ERROR).apply();
if (response.retryAfter != null && !response.retryAfter.contains(":")) {
outIntent.putExtra(EXTRA_RETRY_AFTER, Long.parseLong(response.retryAfter));
}
} else {
outIntent.putExtra(EXTRA_UNREGISTERED, app);
getSharedPreferences().edit().putString(app + ":" + PackageUtils.firstSignatureDigest(this, app), REMOVED).apply();
}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright 2013-2015 microG Project Team
*
* 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 org.microg.gms.ui;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.google.android.gms.R;
import org.microg.gms.gcm.GcmData;
import org.microg.gms.gcm.PushRegisterService;
import java.util.ArrayList;
public class GcmRegisteredAppsFragment extends Fragment {
private AppsAdapter appsAdapter = null;
private GcmData gcmStorage = null;
@Override
@NonNull
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
gcmStorage = new GcmData(getContext());
View view = inflater.inflate(R.layout.gcm_apps_list, container, false);
ListView listView = (ListView) view.findViewById(R.id.list_view);
registerForContextMenu(listView);
listView.setAdapter(updateListView());
return listView;
}
@Override
public void onResume() {
super.onResume();
((ListView) getView().findViewById(R.id.list_view)).setAdapter(updateListView());
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
GcmData.AppInfo appInfo = appsAdapter.getItem(((AdapterView.AdapterContextMenuInfo) menuInfo).position);
MenuInflater menuInflater = getActivity().getMenuInflater();
menuInflater.inflate(R.menu.gcm_app, menu);
PackageManager packageManager = getContext().getPackageManager();
try {
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(appInfo.app, 0);
menu.setHeaderTitle(packageManager.getApplicationLabel(applicationInfo));
} catch (PackageManager.NameNotFoundException e) {
menu.setHeaderTitle(appInfo.app);
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
final GcmData.AppInfo appInfo = appsAdapter.getItem(info.position);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
PushRegisterService.unregister(getContext(), appInfo.app, appInfo.appSignature, null, null);
return null;
}
protected void onPostExecute(Void result) {
((ListView) getView().findViewById(R.id.list_view)).setAdapter(updateListView());
}
}.execute();
return true;
}
synchronized public AppsAdapter updateListView() {
ArrayList<GcmData.AppInfo> registeredApps = new ArrayList<GcmData.AppInfo>();
for (GcmData.AppInfo appInfo : gcmStorage.getAppsInfo()) {
if (appInfo.isRegistered()) {
registeredApps.add(appInfo);
}
}
appsAdapter = new AppsAdapter(getContext(), registeredApps.toArray(new GcmData.AppInfo[registeredApps.size()]));
return appsAdapter;
}
private class AppsAdapter extends ArrayAdapter<GcmData.AppInfo> {
public AppsAdapter(Context context, GcmData.AppInfo[] libraries) {
super(context, R.layout.gcm_app, R.id.title, libraries);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = super.getView(position, convertView, parent);
ImageView image = (ImageView) v.findViewById(R.id.image);
TextView title = (TextView) v.findViewById(R.id.title);
TextView sub = (TextView) v.findViewById(R.id.sub);
TextView warning = (TextView) v.findViewById(R.id.warning);
GcmData.AppInfo appInfo = getItem(position);
PackageManager packageManager = getContext().getPackageManager();
try {
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(appInfo.app, 0);
title.setText(packageManager.getApplicationLabel(applicationInfo).toString());
image.setImageDrawable(packageManager.getApplicationIcon(applicationInfo));
if (appInfo.hasUnregistrationError()) {
warning.setVisibility(View.VISIBLE);
warning.setText(getString(R.string.gcm_app_error_unregistering));
}
} catch (PackageManager.NameNotFoundException e) {
title.setText(appInfo.app);
warning.setVisibility(View.VISIBLE);
warning.setText(getString(R.string.gcm_app_not_installed_anymore));
}
sub.setText(getString(R.string.gcm_messages_received_no, gcmStorage.getAppMessageCount(appInfo.app)));
return v;
}
}
}

View File

@ -87,6 +87,18 @@ public class SettingsActivity extends AppCompatActivity {
return true;
}
});
findPreference(getString(R.string.pref_gcm_apps))
.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
getFragmentManager().beginTransaction()
.addToBackStack("root")
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.replace(R.id.content_wrapper, new GcmRegisteredAppsFragment())
.commit();
return true;
}
});
}
}

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2013-2015 microG Project Team
~
~ 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/listPreferredItemHeight"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:paddingLeft="?attr/listPreferredItemPaddingLeft" >
<ImageView android:id="@+id/image"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:minHeight="40dp"
android:maxHeight="40dp"
android:minWidth="40dp"
android:maxWidth="40dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"/>
<LinearLayout
android:orientation="vertical"
android:layout_marginLeft="10dp"
android:gravity="center"
android:minHeight="?attr/listPreferredItemHeight"
android:layout_height="wrap_content"
android:layout_width="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView android:id="@+id/title"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginRight="8dp"
android:textAppearance="?android:attr/textAppearanceListItem" />
<TextView android:id="@+id/warning"
android:visibility="gone"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textColor="@android:color/holo_red_dark"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
tools:targetApi="lollipop" />
</LinearLayout>
<TextView android:id="@+id/sub"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textColor="@android:color/darker_gray"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
tools:targetApi="lollipop" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2013-2015 microG Project Team
~
~ 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.
-->
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_view"
android:layout_height="match_parent"
android:layout_width="match_parent">
</ListView>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2013-2015 microG Project Team
~
~ 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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/delete"
android:title="@string/gcm_unregister_app" />
</menu>

View File

@ -63,6 +63,10 @@ This can take a couple of minutes."</string>
<string name="pref_gcm_heartbeat_title">Cloud Messaging heartbeat interval</string>
<string name="pref_gcm_heartbeat_summary">The interval in seconds for the system to heartbeat the Google servers. Increasing this number will reduce battery consumption, but might cause delays on push messages.</string>
<string name="pref_gcm_apps">gcm_apps</string>
<string name="pref_gcm_apps_title">Apps using Google Cloud Messaging</string>
<string name="pref_gcm_apps_summary">List of apps currently registered for Google Cloud Messaging.</string>
<string name="prefcat_components">Components</string>
<string name="prefcat_location_service">Location service</string>
@ -101,4 +105,9 @@ This can take a couple of minutes."</string>
<string name="place_picker_nearby_places">Nearby places</string>
<string name="place_picker_location_lat_lng">(%1$.7f, %2$.7f)</string>
<string name="gcm_app_error_unregistering">Error unregistering</string>
<string name="gcm_app_not_installed_anymore">No longer installed</string>
<string name="gcm_messages_received_no">%1$s messages received</string>
<string name="gcm_unregister_app">Unregister</string>
</resources>

View File

@ -40,6 +40,12 @@
android:key="@string/pref_gcm_heartbeat"
android:summary="@string/pref_gcm_heartbeat_summary"
android:title="@string/pref_gcm_heartbeat_title"/>
<Preference
android:dependency="@string/pref_gcm_enable_mcs"
android:key="@string/pref_gcm_apps"
android:summary="@string/pref_gcm_apps_summary"
android:title="@string/pref_gcm_apps_title">
</Preference>
</PreferenceCategory>
<PreferenceCategory android:title="@string/prefcat_location_service">
<Preference android:title="@string/nlp_settings_label">