mirror of https://github.com/perkeep/perkeep.git
clients/android: add multiple profiles feature
When one has/uses several Camlistore servers, it is tedious to change all the configuration fields (host name, user, pass, certificate) whenever one wants to upload to a different server from the one that is currently configured. Therefore, this change adds a new entry to the main menu that allows to create new profiles, and to switch between profiles. Each profile has its own configuration file, which enables to switch between servers effortlessly once they're all configured. https://storage.googleapis.com/camlistore-screenshots/Screenshot_20170606-151931.png https://storage.googleapis.com/camlistore-screenshots/Screenshot_20170606-152007.png https://storage.googleapis.com/camlistore-screenshots/Screenshot_20170606-152026.png https://storage.googleapis.com/camlistore-screenshots/Screenshot_20170606-152041.png Mininum SDK API increased to 11 because of SharedPreferences's getStringSet. Change-Id: I52e2de9e67e84188b1a4b16e046a0d47a35efc62
This commit is contained in:
parent
68cd59797a
commit
8a992c4652
|
@ -1,4 +1,6 @@
|
|||
all:
|
||||
# to run if you already have a functional android development
|
||||
# environment, and you don't need the one in docker.
|
||||
./check-environment.pl
|
||||
./gradlew assembleRelease
|
||||
|
||||
|
@ -14,6 +16,11 @@ dockerdebug:
|
|||
dockerrelease:
|
||||
docker run --rm -v $(GOPATH)/src/camlistore.org:/home/gopher/src/camlistore.org -v $(HOME)/.gradle:/home/gopher/.gradle -v $(HOME)/.android:/home/gopher/.android -w /home/gopher/src/camlistore.org/clients/android --name camlidroid -i -t camlistore/android go run build-in-docker.go -release=true
|
||||
|
||||
# just for documentation, as make is not actually installed in the docker image.
|
||||
debug:
|
||||
# when within the env dev (i.e. after make dockerdev)
|
||||
./gradlew assembleDebug
|
||||
|
||||
installdebug:
|
||||
adb install -r app/build/outputs/apk/app-debug.apk
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId "org.camlistore"
|
||||
minSdkVersion 10
|
||||
minSdkVersion 11
|
||||
targetSdkVersion 17
|
||||
versionCode 1
|
||||
versionName "1"
|
||||
|
|
|
@ -49,6 +49,9 @@
|
|||
|
||||
<activity android:name=".SettingsActivity">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ProfilesActivity">
|
||||
</activity>
|
||||
|
||||
<receiver android:name=".OnBootReceiver">
|
||||
<intent-filter>
|
||||
|
|
|
@ -46,6 +46,7 @@ public class CamliActivity extends Activity {
|
|||
private static final int MENU_STOP_DIE = 3;
|
||||
private static final int MENU_UPLOAD_ALL = 4;
|
||||
private static final int MENU_VERSION = 5;
|
||||
private static final int MENU_PROFILES = 6;
|
||||
|
||||
private IUploadService mServiceStub = null;
|
||||
private IStatusCallback mCallback = null;
|
||||
|
@ -251,6 +252,10 @@ public class CamliActivity extends Activity {
|
|||
MenuItem stopDie = menu.add(Menu.NONE, MENU_STOP_DIE, 0, R.string.stop_die);
|
||||
stopDie.setIcon(android.R.drawable.ic_menu_close_clear_cancel);
|
||||
|
||||
MenuItem profiles = menu.add(Menu.NONE, MENU_PROFILES, 0, R.string.profile);
|
||||
// TODO(mpl): do we care about this icon? I don't even know where it actually appears.
|
||||
profiles.setIcon(android.R.drawable.ic_menu_preferences);
|
||||
|
||||
MenuItem settings = menu.add(Menu.NONE, MENU_SETTINGS, 0, R.string.settings);
|
||||
settings.setIcon(android.R.drawable.ic_menu_preferences);
|
||||
|
||||
|
@ -275,6 +280,9 @@ public class CamliActivity extends Activity {
|
|||
case MENU_SETTINGS:
|
||||
SettingsActivity.show(this);
|
||||
break;
|
||||
case MENU_PROFILES:
|
||||
ProfilesActivity.show(this);
|
||||
break;
|
||||
case MENU_VERSION:
|
||||
Toast.makeText(this, "camput version: " + ((UploadApplication) getApplication()).getCamputVersion(), Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
|
@ -307,7 +315,7 @@ public class CamliActivity extends Activity {
|
|||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
SharedPreferences sp = getSharedPreferences(Preferences.NAME, 0);
|
||||
SharedPreferences sp = getSharedPreferences(Preferences.filename(this.getBaseContext()), 0);
|
||||
try {
|
||||
HostPort hp = new HostPort(sp.getString(Preferences.HOST, ""));
|
||||
if (!hp.isValid()) {
|
||||
|
|
|
@ -16,11 +16,22 @@ limitations under the License.
|
|||
|
||||
package org.camlistore;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
public final class Preferences {
|
||||
private static final String TAG = "Preferences";
|
||||
public static final String NAME = "CamliUploader";
|
||||
|
||||
// key/value store file where we keep the profile names
|
||||
public static final String PROFILES_FILE = "CamliUploader_profiles";
|
||||
// key to the set of profile names
|
||||
public static final String PROFILES = "camli.profiles";
|
||||
// key to the currently selected profile
|
||||
public static final String PROFILE = "camli.profile";
|
||||
// for the preference element that lets us create a new profile name
|
||||
public static final String NEWPROFILE = "camli.newprofile";
|
||||
|
||||
public static final String HOST = "camli.host";
|
||||
// TODO(mpl): list instead of single string later? seems overkill for now.
|
||||
public static final String TRUSTED_CERT = "camli.trusted_cert";
|
||||
|
@ -42,6 +53,18 @@ public final class Preferences {
|
|||
mSP = prefs;
|
||||
}
|
||||
|
||||
// filename returns the settings file name for the currently selected profile.
|
||||
public static String filename(Context ctx) {
|
||||
SharedPreferences profiles = ctx.getSharedPreferences(PROFILES_FILE, 0);
|
||||
String currentProfile = profiles.getString(Preferences.PROFILE, "default");
|
||||
if (currentProfile.equals("default")) {
|
||||
// Special case: we keep CamliUploader as the conf file name by default, to stay
|
||||
// backwards compatible.
|
||||
return NAME;
|
||||
}
|
||||
return NAME+"."+currentProfile;
|
||||
}
|
||||
|
||||
public boolean autoRequiresPower() {
|
||||
return mSP.getBoolean(AUTO_REQUIRE_POWER, false);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
Copyright 2017 The Camlistore Authors.
|
||||
|
||||
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.camlistore;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.os.Bundle;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.Preference.OnPreferenceChangeListener;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.util.Log;
|
||||
|
||||
public class ProfilesActivity extends PreferenceActivity {
|
||||
private static final String TAG = "ProfilesActivity";
|
||||
private ListPreference mProfilePref;
|
||||
private EditTextPreference mNewProfilePref;
|
||||
private SharedPreferences mSharedPrefs;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mSharedPrefs = getSharedPreferences(Preferences.PROFILES_FILE, 0);
|
||||
getPreferenceManager().setSharedPreferencesName(Preferences.PROFILES_FILE);
|
||||
// In effect, I think the default values from arrays.xml are useless since we
|
||||
// always override them with refreshProfileRef right after.
|
||||
addPreferencesFromResource(R.xml.profiles);
|
||||
mProfilePref = (ListPreference) findPreference(Preferences.PROFILE);
|
||||
refreshProfileRef();
|
||||
mNewProfilePref = (EditTextPreference) findPreference(Preferences.NEWPROFILE);
|
||||
|
||||
OnPreferenceChangeListener onChange = new OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference pref, Object newValue) {
|
||||
// Note: newValue isn't yet persisted, but easiest to update the
|
||||
// UI here.
|
||||
if (!(newValue instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
String newStr = (String) newValue;
|
||||
if (pref == mProfilePref) {
|
||||
updateProfilesSummary(newStr);
|
||||
} else if (pref == mNewProfilePref) {
|
||||
updateProfilesList(newStr);
|
||||
return false; // do not actually persist it.
|
||||
}
|
||||
// TODO(mpl): some way to remove a profile.
|
||||
return true; // yes, persist it
|
||||
}
|
||||
};
|
||||
mProfilePref.setOnPreferenceChangeListener(onChange);
|
||||
mNewProfilePref.setOnPreferenceChangeListener(onChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
refreshProfileRef();
|
||||
updatePreferenceSummaries();
|
||||
}
|
||||
|
||||
private void updatePreferenceSummaries() {
|
||||
updateProfilesSummary(mProfilePref.getValue());
|
||||
}
|
||||
|
||||
private void updateProfilesSummary(String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
mProfilePref.setSummary(value);
|
||||
}
|
||||
|
||||
// updateProfilesList adds value to the set of existing profiles to the
|
||||
// key/value store, and refreshes the preference list.
|
||||
private void updateProfilesList(String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
String removedChars = "(%|\\?|:|\"|\\*|\\||/|\\|<|>| )";
|
||||
value = value.replaceAll(removedChars, "");
|
||||
if (value.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> profiles = mSharedPrefs.getStringSet(Preferences.PROFILES, new HashSet<String>());
|
||||
profiles.add(value);
|
||||
Editor ed = mSharedPrefs.edit();
|
||||
ed.putStringSet(Preferences.PROFILES, profiles);
|
||||
ed.commit();
|
||||
refreshProfileRef();
|
||||
mProfilePref.setValue(value);
|
||||
mProfilePref.setSummary(value);
|
||||
Log.v(TAG, "New profile added: " + value);
|
||||
}
|
||||
|
||||
// refreshProfileRef refreshes the profiles preference list with the profile
|
||||
// values stored in the key/value file.
|
||||
private void refreshProfileRef() {
|
||||
Set<String> profiles = mSharedPrefs.getStringSet(Preferences.PROFILES, new HashSet<String>());
|
||||
if (profiles.isEmpty()) {
|
||||
// make sure there's always at least the "default" profile.
|
||||
profiles.add("default");
|
||||
Editor ed = mSharedPrefs.edit();
|
||||
ed.putStringSet(Preferences.PROFILES, profiles);
|
||||
ed.commit();
|
||||
}
|
||||
CharSequence[] listValues = profiles.toArray(new String[]{});
|
||||
mProfilePref.setEntries(listValues);
|
||||
mProfilePref.setEntryValues(listValues);
|
||||
}
|
||||
|
||||
// Convenience method.
|
||||
static void show(Context context) {
|
||||
final Intent intent = new Intent(context, ProfilesActivity.class);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
|
@ -90,12 +90,10 @@ public class SettingsActivity extends PreferenceActivity {
|
|||
m.put(Preferences.MAX_CACHE_MB, "maxCacheSize");
|
||||
prefToParam = Collections.unmodifiableMap(m);
|
||||
|
||||
getPreferenceManager().setSharedPreferencesName(Preferences.NAME);
|
||||
getPreferenceManager().setSharedPreferencesName(Preferences.filename(this.getBaseContext()));
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
hostPref = (EditTextPreference) findPreference(Preferences.HOST);
|
||||
// TODO(mpl): popup window that proposes to automatically add the cert to
|
||||
// the prefs when we fail to dial an untrusted server (and only in that case).
|
||||
trustedCertPref = (EditTextPreference) findPreference(Preferences.TRUSTED_CERT);
|
||||
usernamePref = (EditTextPreference) findPreference(Preferences.USERNAME);
|
||||
passwordPref = (EditTextPreference) findPreference(Preferences.PASSWORD);
|
||||
|
@ -104,7 +102,7 @@ public class SettingsActivity extends PreferenceActivity {
|
|||
maxCacheSizePref = (EditTextPreference) findPreference(Preferences.MAX_CACHE_MB);
|
||||
devIPPref = (EditTextPreference) findPreference(Preferences.DEV_IP);
|
||||
|
||||
mSharedPrefs = getSharedPreferences(Preferences.NAME, 0);
|
||||
mSharedPrefs = getSharedPreferences(Preferences.filename(this.getBaseContext()), 0);
|
||||
mPrefs = new Preferences(mSharedPrefs);
|
||||
|
||||
// Display defaults.
|
||||
|
|
|
@ -107,7 +107,7 @@ public class UploadService extends Service {
|
|||
mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
|
||||
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
mPrefs = new Preferences(getSharedPreferences(Preferences.NAME, 0));
|
||||
mPrefs = new Preferences(getSharedPreferences(Preferences.filename(this.getBaseContext()), 0));
|
||||
|
||||
updateBackgroundWatchers();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="profiles_entries">
|
||||
<item>default</item>
|
||||
</string-array>
|
||||
<string-array name="profiles_entry_values">
|
||||
<item>default</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
|
@ -5,6 +5,8 @@
|
|||
<string name="settings_confirmation_dialog_title">Use these settings?</string>
|
||||
<string name="settings_host_title">Camlistore server</string>
|
||||
<string name="settings_host_summary">e.g. https://foo.example.com or "example.com:3179"</string>
|
||||
<string name="profiles_title">Current profile</string>
|
||||
<string name="profiles_summary">default</string>
|
||||
<string name="settings_qr_title">QR</string>
|
||||
<string name="settings_qr_summary">Scan QR code from /ui/mobile.html</string>
|
||||
<string name="settings_trusted_cert_title">Self-signed cert fingerprint</string>
|
||||
|
@ -31,6 +33,7 @@
|
|||
<string name="files_uploaded">Files Uploaded</string>
|
||||
<string name="bytes_uploaded">Bytes Uploaded</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="profile">Profile</string>
|
||||
<string name="upload_all">Upload All</string>
|
||||
<string name="browse">Browse</string>
|
||||
<string name="results">Results</string>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:key="profilescreen" >
|
||||
|
||||
<ListPreference
|
||||
android:key="camli.profile"
|
||||
android:persistent="true"
|
||||
android:summary="@string/profiles_summary"
|
||||
android:title="@string/profiles_title"
|
||||
android:entries="@array/profiles_entries"
|
||||
android:entryValues="@array/profiles_entry_values"
|
||||
android:defaultValue="default"/>
|
||||
|
||||
<EditTextPreference
|
||||
android:key="camli.newprofile"
|
||||
android:persistent="false"
|
||||
android:summary="Create a new profile"
|
||||
android:title="New Profile" />
|
||||
|
||||
</PreferenceScreen>
|
Loading…
Reference in New Issue