org.cloudfoundry.android.cfdroid.applications.ApplicationControlFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudfoundry.android.cfdroid.applications.ApplicationControlFragment.java

Source

/*
 * Copyright 2012 the original author or 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.cloudfoundry.android.cfdroid.applications;

import org.cloudfoundry.android.cfdroid.CloudFoundry;
import org.cloudfoundry.android.cfdroid.R;
import org.cloudfoundry.android.cfdroid.support.AsyncLoader;
import org.cloudfoundry.android.cfdroid.support.DeferredContentFragment;
import org.cloudfoundry.android.cfdroid.support.TaskWithDialog;
import org.cloudfoundry.android.cfdroid.support.masterdetail.DetailPaneEventsCallback;
import org.cloudfoundry.client.lib.CloudApplication;
import org.cloudfoundry.client.lib.CloudApplication.AppState;
import org.cloudfoundry.client.lib.CloudInfo;

import roboguice.inject.InjectView;
import roboguice.util.Ln;
import android.database.ContentObserver;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.Loader;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;

import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.google.inject.Inject;

/**
 * A fragment for controlling a particular app. Allows start/stop as well as
 * changing number of instances and memory quota.
 * 
 * Assumes that memory management is done in powers of 2.
 * 
 * @author Eric Bottard
 * 
 */
public class ApplicationControlFragment extends DeferredContentFragment<ApplicationControlFragment.AsyncResult>
        implements DetailPaneEventsCallback {

    /* default */static class AsyncResult {
        private CloudInfo cloudInfo;

        private int[] memoryOptions;
    }

    private static int log2(int n) {
        if (n <= 0)
            throw new IllegalArgumentException();
        return 31 - Integer.numberOfLeadingZeros(n);
    }

    private int position;

    private OnSeekBarChangeListener barsListener = new OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            constrainBars();
            if (!fromUser) {
                return;
            }
            updateData();
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
        }
    };

    /**
     * The lowest memory unit an app can have. Current heuristics for getting
     * that are a bit broken.
     */
    private int baseMemoryUnit;

    @Inject
    private CloudFoundry client;

    private CloudInfo cloudInfo;

    /**
     * Used to react to changes to application(s), eg after start/stop.
     */
    private ContentObserver contentObserver = new ContentObserver(null) {
        @Override
        public void onChange(boolean selfChange) {
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Ln.d("ControlFragment received new data");
                    fullyRedrawWidgets();
                }
            });
        }
    };

    /**
     * This is to enable/diable the save button.
     */
    private int initialInstances, initialMemory;

    @InjectView(R.id.instances_seekbar)
    private SeekBar instancesSeekBar;

    @InjectView(R.id.instances)
    private TextView instancestv;

    /**
     * This is the (total) maximum memory that this app could take out of this
     * cloud, once other apps have taken their share.
     */
    private int maxWithoutOthers;

    @InjectView(R.id.memory_seekbar)
    private SeekBar memorySeekBar;

    @InjectView(R.id.memory)
    private TextView memorytv;

    @InjectView(R.id.overall_memory_progressbar)
    private ProgressBar overallMemoryPb;

    @InjectView(R.id.start)
    private View startBtn;

    @InjectView(R.id.status)
    private TextView status;

    @InjectView(R.id.stop)
    private View stopBtn;

    /**
     * Prevents the user from sliding the primary progress ahead of the
     * secondary progress.
     */
    private void constrainBars() {
        if (instancesSeekBar.getProgress() > instancesSeekBar.getSecondaryProgress()) {
            instancesSeekBar.setProgress(instancesSeekBar.getSecondaryProgress());
        }
        if (memorySeekBar.getProgress() > memorySeekBar.getSecondaryProgress()) {
            memorySeekBar.setProgress(memorySeekBar.getSecondaryProgress());
        }
    }

    private void fullyRedrawWidgets() {
        CloudApplication cloudApplication = getCloudApplication();
        initialInstances = cloudApplication.getInstances();
        initialMemory = cloudApplication.getMemory();

        AppState state = cloudApplication.getState();
        startBtn.setEnabled(state != AppState.STARTED);
        stopBtn.setEnabled(state == AppState.STARTED);
        status.setText(state.toString());
        status.setTextColor(getActivity().getResources().getColor(ApplicationView.COLORS.get(state)));

        int maxTotalMemory = cloudInfo.getLimits().getMaxTotalMemory();
        int usedByOtherApps = cloudInfo.getUsage().getTotalMemory() - initialInstances * initialMemory;
        maxWithoutOthers = maxTotalMemory - usedByOtherApps;

        instancesSeekBar.setMax(instancesR2P(maxTotalMemory / baseMemoryUnit));
        instancesSeekBar.setProgress(instancesR2P(cloudApplication.getInstances()));
        instancesSeekBar.setOnSeekBarChangeListener(barsListener);

        memorySeekBar.setMax(memR2P(maxTotalMemory));
        memorySeekBar.setProgress(memR2P(cloudApplication.getMemory()));
        memorySeekBar.setOnSeekBarChangeListener(barsListener);

        overallMemoryPb.setMax(maxTotalMemory);
        overallMemoryPb.setProgress(usedByOtherApps);
        updateData();
    }

    private CloudApplication getCloudApplication() {
        return client.getApplications(false).get(position);
    }

    private int instances() {
        return instancesP2R(instancesSeekBar.getProgress());
    }

    /**
     * Convert from seekbar domain (0 - x) to instances count domain (1 - x+1)
     */
    private int instancesP2R(int p) {
        return p + 1;
    }

    /**
     * Convert from instances count (1 - x) to progress domain (0 - x-1).
     */
    private int instancesR2P(int i) {
        return i - 1;
    }

    /*
     * To be run on a background thread.
     */
    private AsyncResult latestRemoteState() {
        AsyncResult result = new AsyncResult();
        result.cloudInfo = client.getCloudInfo();
        result.memoryOptions = client.getApplicationMemoryChoices();
        return result;
    }

    private int memory() {
        return memP2R(memorySeekBar.getProgress());
    }

    /**
     * Convert from seekbar domain (0 - i) to memory domain (64 - 2048).
     */
    private int memP2R(int p) {
        return baseMemoryUnit * (1 << p);
    }

    /**
     * Convert from mem progress domain to actual memory domain.
     */
    private int memR2P(int mem) {
        return log2((mem / baseMemoryUnit));
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            position = savedInstanceState.getInt("position");
        }
    }

    @Override
    public Loader<AsyncResult> onCreateLoader(int arg0, Bundle arg1) {
        return new AsyncLoader<AsyncResult>(getActivity()) {
            @Override
            public AsyncResult loadInBackground() {
                return latestRemoteState();
            }

        };
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.app_control, menu);
        menu.findItem(R.id.cloud_apply).setEnabled(false);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.application_control, container, false);
    }

    @Override
    public void onDataAvailable(AsyncResult data) {
        baseMemoryUnit = data.memoryOptions[0];
        cloudInfo = data.cloudInfo;
        fullyRedrawWidgets();
        setHasOptionsMenu(true); // first time
        // The following while we're targeting api<11
        getSherlockActivity().invalidateOptionsMenu();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (R.id.cloud_apply == item.getItemId()) {
            saveChanges();
            return true;
        } else {
            return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        menu.findItem(R.id.cloud_apply).setEnabled(userChangedSettings());
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("position", position);
    }

    @Override
    public void onStart() {
        super.onStart();
        client.listenForApplicationsUpdates(contentObserver);
    }

    @Override
    public void onStop() {
        client.stopListeningForApplicationUpdates(contentObserver);
        super.onStop();
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        startBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                start();
            }

        });
        stopBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stop();
            }
        });
    }

    private void saveChanges() {
        new TaskWithDialog<AsyncResult>(getActivity(), R.string.working) {
            @Override
            public AsyncResult call() throws Exception {
                if (memory() != initialMemory) {
                    client.updateApplicationMemory(getCloudApplication().getName(), memory());
                    // Mem changes need restart to take effect
                    if (getCloudApplication().getState() == AppState.STARTED) {
                        client.stopApplication(getCloudApplication().getName());
                        client.startApplication(getCloudApplication().getName());
                    }
                }
                if (instances() != initialInstances) {
                    client.updateApplicationInstances(getCloudApplication().getName(), instances());
                }
                // Need to re-query eg max maxWithoutOthers
                return latestRemoteState();
            }

            protected void onSuccess(AsyncResult t) throws Exception {
                onDataAvailable(t);
            };

        }.execute();
    }

    @Override
    public void selectionChanged(int position) {
        this.position = position;
        if (isResumed()) {
            fullyRedrawWidgets();
            // The following while we're targeting api<11
            getSherlockActivity().invalidateOptionsMenu();
        }
    }

    private void start() {
        new TaskWithDialog<Void>(getActivity(), R.string.working) {
            @Override
            public Void call() throws Exception {
                client.startApplication(getCloudApplication().getName());
                return null;
            }
        }.execute();
    }

    private void stop() {
        new TaskWithDialog<Void>(getActivity(), R.string.working) {
            @Override
            public Void call() throws Exception {
                client.stopApplication(getCloudApplication().getName());
                return null;
            }
        }.execute();
    }

    /**
     * Updates widgets (notably limits conveyed by seekbars secondary progress)
     * to reflect new data.
     */
    private void updateData() {
        memorySeekBar.setSecondaryProgress(memR2P(maxWithoutOthers / instances()));
        instancestv.setText("" + instances());

        instancesSeekBar.setSecondaryProgress(instancesR2P(maxWithoutOthers / memory()));
        overallMemoryPb.setSecondaryProgress(usedByOtherApps() + memory() * instances());
        memorytv.setText("" + memory());
        getSherlockActivity().invalidateOptionsMenu();

    }

    private int usedByOtherApps() {
        return overallMemoryPb.getProgress();
    }

    private boolean userChangedSettings() {
        return instances() != initialInstances || memory() != initialMemory;
    }

}