/* * Copyright (c) 2011, 2012 Roberto Tyley * * This file is part of 'Agit' - an Android Git client. * * Agit is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Agit is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/ . */ package com.madgag.agit; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION_CODES.GINGERBREAD; import static android.os.Build.VERSION_CODES.HONEYCOMB; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_SHORT; import static com.madgag.agit.GitIntents.EXTRA_TARGET_DIR; import static com.madgag.agit.GitIntents.sourceUriFrom; import static com.madgag.agit.GitOperationsService.cloneOperationIntentFor; import static com.madgag.agit.R.string.clone_launcher_activity_title; import static com.madgag.agit.R.string.clone_readiness_not_enough_room_on_partition; import static com.madgag.agit.R.string.clone_readiness_repository_folder_already_exists; import static com.madgag.agit.R.string.ssh_agent_not_correctly_installed; import static com.madgag.agit.RepositoryViewerActivity.manageRepoIntent; import static com.madgag.agit.git.TransportProtocols.niceProtocolNameFor; import static com.madgag.android.ActionBarUtil.fixImageTilingOn; import static com.madgag.android.ActionBarUtil.homewardsWith; import static com.madgag.android.ClickableText.BOLD_LINK_STYLE; import static org.apache.commons.io.FileUtils.byteCountToDisplaySize; import static org.eclipse.jgit.lib.Constants.DOT_GIT_EXT; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.text.Editable; import android.text.SpannableStringBuilder; import android.text.TextWatcher; import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.view.MenuItem; import com.github.rtyley.android.sherlock.roboguice.activity.RoboSherlockActivity; import com.madgag.android.ClickableText; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import org.eclipse.jgit.lib.RepositoryCache; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.util.FS; import roboguice.inject.InjectView; public class CloneLauncherActivity extends RoboSherlockActivity { private final static String TAG = "CloneLauncherActivity"; private static final long MIN_REQUIRED_SPACE = 128L * 1024L; public static Intent cloneLauncherIntentFor(String sourceUri) { return new GitIntentBuilder("clone.PREPARE").sourceUri(sourceUri).toIntent(); } private final boolean layoutTransitionAvailable = Build.VERSION.SDK_INT >= HONEYCOMB; @InjectView(R.id.BareRepo) CheckBox bareRepoCheckbox; @InjectView(R.id.GoCloneButton) Button button; @InjectView(R.id.UseDefaultGitDirLocation) CheckBox useDefaultGitDirLocationButton; @InjectView(R.id.ProtocolLabel) TextView protocolLabel; @InjectView(R.id.secondary_details) ViewGroup secondaryDetailsGroup; @InjectView(R.id.CloneReadinessMessage) TextView cloneReadinessMessageView; @InjectView(R.id.GitDirEditText) EditText gitDirEditText; @InjectView(R.id.CloneUrlEditText) EditText cloneUrlEditText; /** * Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); fixImageTilingOn(getSupportActionBar()); setContentView(R.layout.clone_launcher); ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setTitle(clone_launcher_activity_title); button.setOnClickListener(goCloneButtonListener); OnCheckedChangeListener checkBoxChangeListener = new OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { updateUIWithValidation(); } }; useDefaultGitDirLocationButton.setOnCheckedChangeListener(checkBoxChangeListener); bareRepoCheckbox.setOnCheckedChangeListener(checkBoxChangeListener); TextWatcher watcher = new TextWatcher() { public void onTextChanged(CharSequence text, int arg1, int arg2, int arg3) { } public void beforeTextChanged(CharSequence text, int arg1, int arg2, int arg3) { } public void afterTextChanged(Editable gitDirEditText) { updateUIWithValidation(); } }; cloneUrlEditText.addTextChangedListener(watcher); gitDirEditText.addTextChangedListener(watcher); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: return homewardsWith(this, new Intent(this, DashboardActivity.class)); } return super.onOptionsItemSelected(item); } protected void updateUIWithValidation() { boolean enableClone = true; String cloneUriText = getCloneUriText(); Log.d(TAG, "cloneUriText=" + cloneUriText); CharSequence message = null; boolean cloneUrlPopulated = cloneUriText.length() > 3; // GONE causes jerky animation with the 1st layout transition here, I think because of the initial resize secondaryDetailsGroup.setVisibility((cloneUrlPopulated || !layoutTransitionAvailable)?VISIBLE:INVISIBLE); if (!cloneUrlPopulated) { enableClone = false; message = getString(R.string.clone_readiness_needs_user_to_enter_a_url); } URIish cloneUri = null; String transportProtocol = null; try { cloneUri = getCloneUri(); transportProtocol = niceProtocolNameFor(cloneUri); } catch (URISyntaxException e) { enableClone = false; } if (transportProtocol != null) { Log.w(TAG, "Smells like " + transportProtocol); protocolLabel.setText("Protocol: " + transportProtocol); protocolLabel.setVisibility(VISIBLE); if (transportProtocol.equals("SSH")) { boolean sshAgentAvailable = PERMISSION_GRANTED == checkCallingOrSelfPermission("org.openintents.ssh" + ".permission.ACCESS_SSH_AGENT"); Log.d(TAG, "SSH good : " + sshAgentAvailable); if (!sshAgentAvailable) { message = getString(ssh_agent_not_correctly_installed); } } } else { Log.w(TAG, "Don't recognise protocol"); protocolLabel.setVisibility(INVISIBLE); } gitDirEditText.setEnabled(!useDefaultGitDirLocationButton.isChecked()); if (useDefaultGitDirLocationButton.isChecked()) { String requiredText = (cloneUri == null) ? "" : defaultRepoDirFor(cloneUri).getAbsolutePath(); String currentGitDirText = gitDirEditText.getText().toString(); if (!currentGitDirText.equals(requiredText)) gitDirEditText.setText(requiredText); } File f = getTargetFolder(); boolean folderAlreadyExists = f.exists(); if (folderAlreadyExists) { enableClone = false; if (existingRepoGitDir() != null) { message = getString(clone_readiness_repository_folder_already_exists); } else { message = getString(R.string.clone_readiness_folder_already_exists); } } else if (f.getPath().length() > 0) { if (Build.VERSION.SDK_INT >= GINGERBREAD) { long usableSpace = existingFileInPartitionContaining(f).getUsableSpace(); String spaceText = byteCountToDisplaySize(usableSpace); Log.d(TAG, "usableSpace = " + spaceText); if (usableSpace < MIN_REQUIRED_SPACE) { enableClone = false; message = getString(clone_readiness_not_enough_room_on_partition, spaceText); } } } displayHelp(message); button.setEnabled(enableClone); } private File existingFileInPartitionContaining(File f) { while (!f.exists() && f.getParentFile()!=null) { f = f.getParentFile(); } return f; } private void displayHelp(CharSequence message) { cloneReadinessMessageView.setVisibility(message == null ? INVISIBLE : VISIBLE); if (message != null) { Editable spana = new SpannableStringBuilder(message); ClickableText.addLinks(spana, BOLD_LINK_STYLE, new ClickableText.Listener() { public void onClick(String command, View widget) { if (command.equals("specify_target_dir")) { useDefaultGitDirLocationButton.setChecked(false); gitDirEditText.requestFocus(); setCursorToEnd(gitDirEditText); } else if (command.equals("view_existing_repo")) { startActivity(manageRepoIntent(existingRepoGitDir())); } else if (command.equals("view_ssh_instructions")) { startActivity(new Intent(CloneLauncherActivity.this, AboutUsingSshActivity.class)); } else if (command.equals("suggest_repo")) { startActivityForResult(new GitIntentBuilder("repo.SUGGEST").toIntent(), 0); } } }); cloneReadinessMessageView.setText(spana); cloneReadinessMessageView.setMovementMethod(LinkMovementMethod.getInstance()); } } private File existingRepoGitDir() { return RepositoryCache.FileKey.resolve(getTargetFolder(), FS.detect()); } protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK) { setUpUIFromIntent(data); } } @Override protected void onStart() { super.onStart(); Intent intent = getIntent(); setUpUIFromIntent(intent); } private void setUpUIFromIntent(Intent intent) { Log.d(TAG, "setUpUIFromIntent with " + intent); if (intent != null) { setSourceUriFrom(intent); setGitDirFrom(intent); } } private void setSourceUriFrom(Intent intent) { String sourceUri = sourceUriFrom(intent); if (sourceUri != null) { cloneUrlEditText.setText(sourceUri); setCursorToEnd(cloneUrlEditText); Log.d(TAG, "Set cloneUrlEditText to " + sourceUri); } } private static void setCursorToEnd(EditText editText) { editText.setSelection(editText.getText().length()); } private void setGitDirFrom(Intent intent) { String gitdir = intent.getStringExtra(EXTRA_TARGET_DIR); useDefaultGitDirLocationButton.setChecked(gitdir == null); if (gitdir != null) { gitDirEditText.setText(gitdir); Log.d(TAG, "Set gitdir to " + gitdir); } } ; @Override protected void onResume() { super.onResume(); Log.d(TAG, "onResume called"); updateUIWithValidation(); } public URIish getCloneUri() throws URISyntaxException { return new URIish(getCloneUriText()); } private String getCloneUriText() { return cloneUrlEditText.getText().toString(); } public File getTargetFolder() { return new File(gitDirEditText.getText().toString()); } OnClickListener goCloneButtonListener = new OnClickListener() { public void onClick(View v) { URIish uri; try { uri = getCloneUri(); } catch (URISyntaxException e) { Toast.makeText(v.getContext(), "bad dog", 10).show(); return; } File checkoutLocation = getTargetFolder(); boolean bare = bareRepoCheckbox.isChecked(); try { launchClone(uri, checkoutLocation, bare); } catch (Exception e) { Toast.makeText(v.getContext(), "ARRG: " + e, 10).show(); // TODO Auto-generated catch block e.printStackTrace(); } } }; private void launchClone(URIish uri, File repoDir, boolean bare) throws IOException, URISyntaxException { if (!repoDir.mkdirs()) { String message = "Couldn't create " + repoDir; Toast.makeText(CloneLauncherActivity.this, message, LENGTH_LONG).show(); throw new IOException(message); } startService(cloneOperationIntentFor(uri, repoDir, bare)); Toast.makeText(getApplicationContext(), R.string.clone_launcher_farewell_due_to_clone_launched, LENGTH_SHORT).show(); finish(); } private File defaultRepoDirFor(URIish uri) { File reposDir = new File(Environment.getExternalStorageDirectory(), "git-repos"); String suffix = bareRepoCheckbox.isChecked() ? DOT_GIT_EXT : ""; return new File(reposDir, defaultRepoNameFor(uri) + suffix); } private String defaultRepoNameFor(URIish uri) { try { return uri.getHumanishName(); } catch (Exception e) { return "repo"; } } }