Added documentation,

master
Max Ammann 2016-06-13 21:35:21 +02:00
parent 5e5862e059
commit 517186d6d3
10 changed files with 221 additions and 141 deletions

View File

@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "22.0.1"
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "max.music_cyclon"
@ -21,8 +21,10 @@ dependencies {
compile "cz.msebera.android:httpclient:4.4.1.2"
compile 'commons-io:commons-io:2.4'
// compile 'com.android.support:appcompat-v7:23.4.0'
// compile 'com.android.support:preference-v14:23.4.0'
// compile 'com.android.support:appcompat-v7:23.4.0'
// and
// compile 'com.android.support:preference-v14:23.4.0'
// are provided by:
compile 'com.takisoft.fix:preference-v7:23.4.0.4'
compile 'com.android.support:appcompat-v7:23.4.0'

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package max.music_cyclon.slidingtab;
package com.google.samples.apps.iosched.ui.widget;
import android.content.Context;
import android.graphics.Typeface;
@ -311,9 +311,6 @@ public class SlidingTabLayout extends HorizontalScrollView {
public void onClick(View v) {
for (int i = 0; i < mTabStrip.getChildCount(); i++) {
if (v == mTabStrip.getChildAt(i)) {
if (mViewPager.getChildAt(i) == null) {
continue;
}
mViewPager.setCurrentItem(i);
return;
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package max.music_cyclon.slidingtab;
package com.google.samples.apps.iosched.ui.widget;
import android.content.Context;
import android.graphics.Canvas;

View File

@ -1,15 +1,16 @@
package max.music_cyclon;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat;
/**
* The activity to set general activities
*/
public class MainPreferenceActivity extends AppCompatActivity {
@Override
@ -17,17 +18,21 @@ public class MainPreferenceActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_preference);
Toolbar toolbar = (Toolbar) findViewById(R.id.preference_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
ActionBar actionBar = getSupportActionBar();
assert actionBar != null;
actionBar.setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent myIntent = new Intent(getApplicationContext(), SynchronizeActivity.class);
startActivityForResult(myIntent, 0);
return true;
if (item.getItemId() == android.R.id.home) {
Intent myIntent = new Intent(getApplicationContext(), SynchronizeActivity.class);
startActivityForResult(myIntent, 0);
return true;
}
return false;
}
public static class MainPreferenceFragment extends PreferenceFragment {

View File

@ -13,29 +13,31 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Holds the fragments for the configuration of all {@link SynchronizeConfig}s
*/
public class PagerAdapter extends FragmentStatePagerAdapter {
private final List<String> configs = new ArrayList<>();
private final Map<String, Config> configData = new HashMap<>();
private final Map<String, SynchronizeConfig> configData = new HashMap<>();
public PagerAdapter(List<Config> configs , FragmentManager fm) {
public PagerAdapter(List<SynchronizeConfig> configs , FragmentManager fm) {
super(fm);
for (Config config : configs) {
for (SynchronizeConfig config : configs) {
this.configs.add(config.getName());
this.configData.put(config.getName(), config);
}
}
public void save(OutputStream os) throws JSONException, IOException {
Config.save(configData.values(), os);
SynchronizeConfig.save(configData.values(), os);
}
public boolean add(String name) {
configData.put(name, new Config(name));
configData.put(name, new SynchronizeConfig(name));
return configs.add(name);
}
@ -71,7 +73,7 @@ public class PagerAdapter extends FragmentStatePagerAdapter {
return configs;
}
public List<Config> getConfigData() {
public List<SynchronizeConfig> getConfigData() {
return new ArrayList<>(configData.values());
}

View File

@ -2,6 +2,7 @@ package max.music_cyclon;
import android.Manifest;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.Context;
@ -19,39 +20,64 @@ import android.os.RemoteException;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import com.google.samples.apps.iosched.ui.widget.SlidingTabLayout;
import org.json.JSONException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import max.music_cyclon.service.LibraryService;
import max.music_cyclon.slidingtab.SlidingTabLayout;
/**
* The main activity for synchronisation
* <p>
* This class manages:
* <ul>
* <li>
* the {@link PagerAdapter} with the references to all configs and their loading and saving.
* </li>
* <li>the link to the {@link LibraryService} with bi-directional message dispatching</li>
* <li>the general layout</li>
* <li>permission requests</li>
* </ul>
*
*/
public class SynchronizeActivity extends AppCompatActivity {
private PagerAdapter pagerAdapter;
/** Messenger for communicating with service. */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
private boolean mIsBound;
/**
* Messenger for communicating with service.
*/
private Messenger serviceObject = null;
/**
* Flag indicating whether we have called bind on the service.
*/
private boolean isBound;
/**
* The dialog which is being displayed while a sync is in progress
*/
private ProgressDialog syncProgress = null;
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
private final Messenger mMessenger = new Messenger(new IncomingHandler());
private ProgressDialog syncProgress;
private final Messenger mMessenger = new Messenger(new IncomingHandler(new WeakReference<>(this)));
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -61,15 +87,13 @@ public class SynchronizeActivity extends AppCompatActivity {
Toolbar toolbar = (Toolbar) findViewById(R.id.my_toolbar);
setSupportActionBar(toolbar);
List<Config> configs = Collections.emptyList();
List<SynchronizeConfig> configs = Collections.emptyList();
try {
FileInputStream in = openFileInput("configs.json");
configs = Config.load(in);
configs = SynchronizeConfig.load(in);
in.close();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException | JSONException e) {
Log.e("CONFIG", "Failed loading the config", e);
}
pagerAdapter = new PagerAdapter(configs, getSupportFragmentManager());
@ -83,11 +107,12 @@ public class SynchronizeActivity extends AppCompatActivity {
// Initialize tabs
final SlidingTabLayout tabs = (SlidingTabLayout) findViewById(R.id.tabs);
assert tabs != null;
tabs.setDistributeEvenly(true);
tabs.setCustomTabColorizer(new SlidingTabLayout.TabColorizer() {
@Override
public int getIndicatorColor(int position) {
return getResources().getColor(R.color.accentColor);
return ContextCompat.getColor(SynchronizeActivity.this, R.color.accentColor);
}
});
@ -111,37 +136,31 @@ public class SynchronizeActivity extends AppCompatActivity {
}
});
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
// Request permissions
requestPermissions();
}
private void requestPermissions() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
0);
}
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
0);
}
}
@Override
protected void onStop() {
super.onStop();
unbindLibraryService();
try {
FileOutputStream fos = openFileOutput("configs.json", Context.MODE_PRIVATE);
getPagerAdapter().save(fos);
fos.close();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException | JSONException e) {
Log.e("CONFIG", "Failed saving the config", e);
}
}
@ -149,6 +168,14 @@ public class SynchronizeActivity extends AppCompatActivity {
return pagerAdapter;
}
public Dialog getSyncProgress() {
return syncProgress;
}
public void clearSyncProgress() {
syncProgress = null;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
@ -162,15 +189,18 @@ public class SynchronizeActivity extends AppCompatActivity {
Intent preferenceIntent = new Intent(this, MainPreferenceActivity.class);
startActivity(preferenceIntent);
return true;
case R.id.action_version:
notYetImplemented(this);
break;
case R.id.action_help:
notYetImplemented(this);
break;
case R.id.action_sync:
Intent intent = new Intent(SynchronizeActivity.this, LibraryService.class);
List<Config> configs = getPagerAdapter().getConfigData();
intent.putExtra("configs", configs.toArray(new Config[configs.size()]));
SynchronizeActivity.this.startService(intent);
startLibraryService();
doBindService();
bindLibraryService();
// Show sync control dialog
syncProgress = new ProgressDialog(SynchronizeActivity.this);
syncProgress.setMessage("Synchronizing");
syncProgress.setCancelable(false);
@ -182,19 +212,36 @@ public class SynchronizeActivity extends AppCompatActivity {
});
syncProgress.show();
return true;
default:
return super.onOptionsItemSelected(item);
}
return false;
}
private class IncomingHandler extends Handler {
public static void notYetImplemented(Context context) {
new AlertDialog.Builder(context).setMessage("Not yet implemented!").show();
}
private static class IncomingHandler extends Handler {
private final WeakReference<SynchronizeActivity> activity;
private IncomingHandler(WeakReference<SynchronizeActivity> activity) {
this.activity = activity;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case LibraryService.MSG_FINISHED:
syncProgress.dismiss();
SynchronizeActivity activity = this.activity.get();
if (activity != null) {
Dialog dialog = activity.getSyncProgress();
if (dialog != null) {
dialog.dismiss();
activity.clearSyncProgress();
}
}
break;
default:
super.handleMessage(msg);
@ -207,12 +254,12 @@ public class SynchronizeActivity extends AppCompatActivity {
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
serviceObject = new Messenger(service);
try {
Message msg = Message.obtain(null, LibraryService.MSG_REGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
serviceObject.send(msg);
} catch (RemoteException ignored) {
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
@ -222,37 +269,39 @@ public class SynchronizeActivity extends AppCompatActivity {
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
serviceObject = null;
}
};
void doBindService() {
private void startLibraryService() {
Intent intent = new Intent(SynchronizeActivity.this, LibraryService.class);
List<SynchronizeConfig> configs = getPagerAdapter().getConfigData();
intent.putExtra("configs", configs.toArray(new SynchronizeConfig[configs.size()]));
SynchronizeActivity.this.startService(intent);
}
private void bindLibraryService() {
bindService(new Intent(
SynchronizeActivity.this,
LibraryService.class
), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
isBound = true;
}
void doUnbindService() {
if (mIsBound) {
// If we have received the service, and hence registered with
// it, then now is the time to unregister.
if (mService != null) {
private void unbindLibraryService() {
if (isBound) {
if (serviceObject != null) {
try {
Message msg = Message.obtain(null,
LibraryService.MSG_UNREGISTER_CLIENT);
Message msg = Message.obtain(null, LibraryService.MSG_UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException e) {
// There is nothing special we need to do if the service
// has crashed.
serviceObject.send(msg);
} catch (RemoteException ignored) {
}
}
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
isBound = false;
}
}
}

View File

@ -15,17 +15,27 @@ import java.util.Iterator;
import java.util.List;
import java.util.Scanner;
public class Config implements Parcelable {
/**
* A synchronize config which holds the information which tracks/albums should be queried
*/
public class SynchronizeConfig implements Parcelable {
/**
* The name of this config
*/
private final String name;
/**
* The data structure
*/
private final JSONObject json;
public Config(String name, JSONObject json) {
public SynchronizeConfig(String name, JSONObject json) {
this.name = name;
this.json = json;
}
public Config(String name) {
public SynchronizeConfig(String name) {
this(name, new JSONObject());
}
@ -72,43 +82,43 @@ public class Config implements Parcelable {
dest.writeString(json.toString());
}
public static final Parcelable.Creator<Config> CREATOR = new Parcelable.Creator<Config>() {
public Config createFromParcel(Parcel in) {
public static final Parcelable.Creator<SynchronizeConfig> CREATOR = new Parcelable.Creator<SynchronizeConfig>() {
public SynchronizeConfig createFromParcel(Parcel in) {
try {
return new Config(in.readString(), new JSONObject(in.readString()));
return new SynchronizeConfig(in.readString(), new JSONObject(in.readString()));
} catch (JSONException e) {
return new Config("none");
return new SynchronizeConfig("none");
}
}
public Config[] newArray(int size) {
return new Config[size];
public SynchronizeConfig[] newArray(int size) {
return new SynchronizeConfig[size];
}
};
public static List<Config> load(InputStream in) throws JSONException {
public static List<SynchronizeConfig> load(InputStream in) throws JSONException {
String data = convertStreamToString(in);
return load(data);
}
public static List<Config> load(String data) throws JSONException {
public static List<SynchronizeConfig> load(String data) throws JSONException {
JSONObject jsonConfigs = new JSONObject(data);
ArrayList<Config> configs = new ArrayList<>();
ArrayList<SynchronizeConfig> configs = new ArrayList<>();
Iterator keys = jsonConfigs.keys();
while (keys.hasNext()) {
String key = (String) keys.next();
configs.add(new Config(key, jsonConfigs.getJSONObject(key)));
configs.add(new SynchronizeConfig(key, jsonConfigs.getJSONObject(key)));
}
return configs;
}
public static void save(Iterable<Config> configs, OutputStream fos) throws JSONException, IOException {
public static void save(Iterable<SynchronizeConfig> configs, OutputStream fos) throws JSONException, IOException {
JSONObject jsonConfigs = new JSONObject();
for (Config config : configs) {
for (SynchronizeConfig config : configs) {
jsonConfigs.put(config.getName(), config.getJson());
}

View File

@ -2,18 +2,24 @@ package max.music_cyclon;
import android.os.Bundle;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import android.support.v7.preference.TwoStatePreference;
import com.takisoft.fix.support.v7.preference.EditTextPreference;
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat;
import org.json.JSONException;
import org.json.JSONObject;
/**
* A fragment which holds one {@link SynchronizeConfig} and displays it's content for
* simple editing.
*/
public class SynchronizeConfigFragment extends PreferenceFragmentCompat {
private String name;
private PagerAdapter pagerAdapter;
private Config config;
private SynchronizeConfig config;
@Override
public void onCreate(final Bundle savedInstanceState) {
@ -24,19 +30,26 @@ public class SynchronizeConfigFragment extends PreferenceFragmentCompat {
ConfigUpdater updater = new ConfigUpdater();
for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
Preference preference = getPreferenceScreen().getPreference(i);
if (config.getJson().has(preference.getKey())) {
JSONObject json = config.getJson();
PreferenceScreen screen = getPreferenceScreen();
for (int i = 0; i < screen.getPreferenceCount(); i++) {
Preference preference = screen.getPreference(i);
if (json.has(preference.getKey())) {
if (preference instanceof TwoStatePreference) {
((TwoStatePreference) preference).setChecked(config.getJson().optBoolean(preference.getKey()));
boolean data = json.optBoolean(preference.getKey());
((TwoStatePreference) preference).setChecked(data);
} else if (preference instanceof EditTextPreference) {
((EditTextPreference) preference).setText(config.getJson().optString(preference.getKey()));
String data = json.optString(preference.getKey());
((EditTextPreference) preference).setText(data);
}
}
preference.setOnPreferenceChangeListener(updater);
}
// The remove listener
Preference removePreference = findPreference("remove");
removePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
@ -64,7 +77,6 @@ public class SynchronizeConfigFragment extends PreferenceFragmentCompat {
return name;
}
public PagerAdapter getPagerAdapter() {
return pagerAdapter;
}
@ -73,21 +85,26 @@ public class SynchronizeConfigFragment extends PreferenceFragmentCompat {
this.pagerAdapter = pagerAdapter;
}
public void setConfig(Config config) {
public void setConfig(SynchronizeConfig config) {
this.config = config;
}
public SynchronizeConfig getConfig() {
return config;
}
private class ConfigUpdater implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object o) {
String key = preference.getKey();
try {
config.getJson().put(key, o);
} catch (JSONException e) {
e.printStackTrace();
}
JSONObject config = getConfig().getJson();
return true;
try {
config.put(key, o);
return true;
} catch (JSONException e) {
return false;
}
}
}
}

View File

@ -24,10 +24,7 @@ import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
@ -43,7 +40,7 @@ import cz.msebera.android.httpclient.client.methods.CloseableHttpResponse;
import cz.msebera.android.httpclient.client.methods.HttpGet;
import cz.msebera.android.httpclient.impl.client.CloseableHttpClient;
import cz.msebera.android.httpclient.impl.client.HttpClients;
import max.music_cyclon.Config;
import max.music_cyclon.SynchronizeConfig;
import max.music_cyclon.service.db.FileTracker;
public class LibraryService extends IntentService {
@ -90,7 +87,7 @@ public class LibraryService extends IntentService {
super("max.music_cyclon.service.LibraryService");
}
public List<Item> fetchRandom(String address, Config config, Resources resources) throws IOException {
public List<Item> fetchRandom(String address, SynchronizeConfig config, Resources resources) throws IOException {
StringBuilder get;
if (config.isAlbum(resources)) {
@ -249,7 +246,7 @@ public class LibraryService extends IntentService {
}
for (Parcelable parcelable : configs) {
Config config = (Config) parcelable;
SynchronizeConfig config = (SynchronizeConfig) parcelable;
List<Item> items;
try {
updater.showMessage("Fetching music information for %s", config.getName());

View File

@ -1,55 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen android:persistent="false" xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:persistent="false">
<EditTextPreference
android:persistent="false"
android:defaultValue="@integer/size"
android:inputType="number"
android:key="size"
android:summary="Approximately amount to download"
android:title="Download size" />
android:persistent="false"
android:summary="The amount of tracks or albums to download"
android:title="Number of items" />
<!--<SwitchPreference-->
<!--android:persistent="false"-->
<!--android:defaultValue="@bool/random"-->
<!--android:key="random"-->
<!--android:summary=""-->
<!--android:title="Select title/albums randomly" />-->
<!--android:persistent="false"-->
<!--android:defaultValue="@bool/random"-->
<!--android:key="random"-->
<!--android:summary=""-->
<!--android:title="Select title/albums randomly" />-->
<SwitchPreference
android:persistent="false"
android:defaultValue="@bool/use_albums"
android:key="use_albums"
android:summary=""
android:title="Download only complete albums" />
android:persistent="false"
android:summary="Whether you want only complete albums"
android:title="Albums" />
<EditTextPreference
android:persistent="false"
android:defaultValue="@string/query"
android:inputType="text"
android:key="query"
android:summary=""
android:title="Query string" />
android:persistent="false"
android:summary="The search term you want to use in this configuration"
android:title="Query" />
<SwitchPreference
android:persistent="false"
android:defaultValue="@bool/start_charging"
android:key="start_charging"
android:persistent="false"
android:summary="This option if selected will allow to start the download if connected with a ac charger"
android:title="Charging starts download" />
<EditTextPreference
android:persistent="false"
android:defaultValue="@integer/download_interval"
android:inputType="number"
android:key="download_interval"
android:summary="Download interval in days"
android:persistent="false"
android:summary="Interval for downloading in days"
android:title="Download interval" />
<Preference
android:persistent="false"
android:key="remove"
android:summary="Remove this config"
android:title="Remove" />
android:persistent="false"
android:title="Remove this config" />
<!--android:icon="@drawable/ic_delete_forever_black_24dp"-->
</PreferenceScreen>