Merge branch 'integration/mr-102-and-107' into 'master'
Integration branch containing both MR 102 and 107 Given both MR !102 and !107 both stood on eachothers feet to some extent, this branch contains all commits from both in an integration branch. If both @Nutomic and @eighthave are happy that it faithfully kept their changes during the minor merge conflict resolutions, then we can merge this instead of those two branches. * !102 changed download progress from events + listeners to broadcasts + receivers. * !107 made use of download progress events + listeners to show a downloading UI that was embedded in the activity, rather than shown as a modal dialog. The conflicts which arose while I merged these branches together were in `AppDetails`. I made it so that, to the best of my ability, it uses broadcast receivers instead of progress listeners when updating the progress bar. Other than that, the only other conflict was both trying to store a reference to the main button from the UI. The only changes were in naming (mainButton vs btMain) and also in the place where the local variable was assigned (onCreate() vs setupViews() which is called during onCreate() anyway). After merging, I did some minor cleanups. This is because in the process of checking that my conflict resolution compiled, I thought it best to remove a bunch of warnings from `AppDetails` and others. Turns out that by doing so, I found a bug due to the integration (to do with the `AppDetails` querying the downloader for status in `onResume()` rather than waiting for a broadcast event) so I'm glad I did so. Let me know what you think, and then after the next stable, we can go ahead and merge this if people are happy. p.s. I have no idea why GitLab is showing @eighthave's commit at the top of the list of commits, after my integration related commits. See merge request !114
This commit is contained in:
commit
2b8a882933
BIN
F-Droid/res/drawable-hdpi/ic_clear.png
Normal file
BIN
F-Droid/res/drawable-hdpi/ic_clear.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 207 B |
BIN
F-Droid/res/drawable-mdpi/ic_clear.png
Normal file
BIN
F-Droid/res/drawable-mdpi/ic_clear.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 164 B |
BIN
F-Droid/res/drawable-xhdpi/ic_clear.png
Normal file
BIN
F-Droid/res/drawable-xhdpi/ic_clear.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 235 B |
BIN
F-Droid/res/drawable-xxhdpi/ic_clear.png
Normal file
BIN
F-Droid/res/drawable-xxhdpi/ic_clear.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 309 B |
BIN
F-Droid/res/drawable-xxxhdpi/ic_clear.png
Normal file
BIN
F-Droid/res/drawable-xxxhdpi/ic_clear.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 377 B |
@ -16,7 +16,7 @@
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/icon_and_title"
|
||||
android:layout_width="match_parent"
|
||||
@ -40,6 +40,7 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="vertical"
|
||||
android:layout_toRightOf="@id/icon"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingStart="16dp">
|
||||
|
||||
@ -109,7 +110,50 @@
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/holder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/icon"
|
||||
android:gravity="center">
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1">
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal" />
|
||||
<TextView
|
||||
android:id="@+id/progress_size"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_below="@id/progress_bar"
|
||||
android:textSize="12sp"/>
|
||||
<TextView
|
||||
android:id="@+id/progress_percentage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@id/progress_bar"
|
||||
android:textSize="12sp"/>
|
||||
</RelativeLayout>
|
||||
<ImageButton
|
||||
android:id="@+id/cancel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="5dp"
|
||||
android:layout_weight="0"
|
||||
android:visibility="gone"
|
||||
android:src="@drawable/ic_clear"
|
||||
android:background="@null"/>
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
|
||||
|
@ -1,32 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="@dimen/padding_top"
|
||||
android:paddingLeft="@dimen/padding_side"
|
||||
android:paddingStart="@dimen/padding_side"
|
||||
android:paddingRight="@dimen/padding_side"
|
||||
android:paddingEnd="@dimen/padding_side">
|
||||
|
||||
<!-- Editable URL of this repo -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/repoView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/label_repo_url"
|
||||
android:text="@string/repo_url"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true" />
|
||||
<EditText
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/input_repo_url"
|
||||
android:inputType="textUri"
|
||||
android:layout_below="@id/label_repo_url" />
|
||||
android:textStyle="bold"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true" />
|
||||
|
||||
<!-- Name of this repo -->
|
||||
<TextView
|
||||
@ -139,3 +139,11 @@
|
||||
android:layout_below="@id/text_not_yet_updated"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qr_code"
|
||||
android:src="@drawable/swap_qr_example"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
@ -7,11 +7,6 @@
|
||||
android:icon="@drawable/ic_search_white"
|
||||
android:title="@string/menu_search"
|
||||
app:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/action_update_repo"
|
||||
android:icon="@drawable/ic_refresh_white"
|
||||
android:title="@string/menu_update_repo"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/action_swap"
|
||||
android:title="@string/swap"
|
||||
@ -26,6 +21,11 @@
|
||||
android:icon="@drawable/ic_bluetooth_white"
|
||||
android:title="@string/menu_send_apk_bt"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/action_update_repo"
|
||||
android:icon="@drawable/ic_refresh_white"
|
||||
android:title="@string/menu_update_repo"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:icon="@drawable/ic_settings_white"
|
||||
|
21
F-Droid/res/menu/repo_details_activity.xml
Normal file
21
F-Droid/res/menu/repo_details_activity.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_update"
|
||||
android:icon="@drawable/ic_refresh_white"
|
||||
android:title="@string/repo_update"
|
||||
app:showAsAction="always|withText" />
|
||||
<item
|
||||
android:id="@+id/menu_delete"
|
||||
android:icon="@drawable/ic_delete_white"
|
||||
android:title="@string/delete"
|
||||
app:showAsAction="ifRoom|withText" />
|
||||
<item
|
||||
android:id="@+id/menu_enable_nfc"
|
||||
android:icon="@drawable/ic_nfc_white"
|
||||
android:title="@string/enable_nfc_send"
|
||||
app:showAsAction="ifRoom|withText" />
|
||||
|
||||
</menu>
|
@ -210,6 +210,7 @@
|
||||
- Percentage complete (int between 0-100)
|
||||
-->
|
||||
<string name="status_download">Downloading\n%2$s / %3$s (%4$d%%) from\n%1$s</string>
|
||||
<string name="update_notification_title">Updating repositories</string>
|
||||
<string name="status_processing_xml_percent">Processing %2$s / %3$s (%4$d%%) from %1$s</string>
|
||||
<string name="status_connecting_to_repo">Connecting to\n%1$s</string>
|
||||
<string name="status_checking_compatibility">Checking apps compatibility with your device…</string>
|
||||
@ -372,4 +373,13 @@
|
||||
|
||||
<string name="perms_new_perm_prefix"><font size="12" fgcolor="#ff33b5e5">NEW: </font></string>
|
||||
<string name="perms_description_app">Provided by %1$s.</string>
|
||||
<string name="downloading">Downloading...</string>
|
||||
|
||||
<string-array name="file_size_units">
|
||||
<item>B</item>
|
||||
<item>KiB</item>
|
||||
<item>MiB</item>
|
||||
<item>GiB</item>
|
||||
<item>TiB</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
@ -21,28 +21,30 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.database.ContentObserver;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.Html;
|
||||
import android.text.Layout;
|
||||
import android.text.Selection;
|
||||
@ -63,9 +65,11 @@ import android.view.Window;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -90,6 +94,7 @@ import org.fdroid.fdroid.net.Downloader;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@ -113,7 +118,7 @@ interface AppInstallListener {
|
||||
void removeApk(String packageName);
|
||||
}
|
||||
|
||||
public class AppDetails extends ActionBarActivity implements ProgressListener, AppDetailsData, AppInstallListener {
|
||||
public class AppDetails extends AppCompatActivity implements ProgressListener, AppDetailsData, AppInstallListener {
|
||||
|
||||
private static final String TAG = "AppDetails";
|
||||
|
||||
@ -124,7 +129,6 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
|
||||
private FDroidApp fdroidApp;
|
||||
private ApkListAdapter adapter;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
private static class ViewHolder {
|
||||
TextView version;
|
||||
@ -316,6 +320,7 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
private App app;
|
||||
private PackageManager mPm;
|
||||
private ApkDownloader downloadHandler;
|
||||
private LocalBroadcastManager localBroadcastManager;
|
||||
|
||||
private boolean startingIgnoreAll;
|
||||
private int startingIgnoreThis;
|
||||
@ -323,11 +328,14 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
private final Context mctx = this;
|
||||
private Installer installer;
|
||||
|
||||
|
||||
private AppDetailsHeaderFragment mHeaderFragment;
|
||||
|
||||
/**
|
||||
* Stores relevant data that we want to keep track of when destroying the activity
|
||||
* with the expectation of it being recreated straight away (e.g. after an
|
||||
* orientation change). One of the major things is that we want the download thread
|
||||
* to stay active, but for it not to trigger any UI stuff (e.g. progress dialogs)
|
||||
* to stay active, but for it not to trigger any UI stuff (e.g. progress bar)
|
||||
* between the activity being destroyed and recreated.
|
||||
*/
|
||||
private static class ConfigurationChangeHelper {
|
||||
@ -418,13 +426,7 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
listFragment.removeSummaryHeader();
|
||||
}
|
||||
|
||||
// Spinner seems to default to visible on Android 4.0.3 and 4.0.4
|
||||
// https://gitlab.com/fdroid/fdroidclient/issues/75
|
||||
// Can't put this in onResume(), because that is called on return from asking
|
||||
// the user permission to use su (in which case we still want to show the
|
||||
// progress indicator after returning from that prompt).
|
||||
setSupportProgressBarIndeterminateVisibility(false);
|
||||
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
}
|
||||
|
||||
// The signature of the installed version.
|
||||
@ -442,39 +444,35 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
myAppObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (downloadHandler != null) {
|
||||
if (downloadHandler.isComplete()) {
|
||||
downloadCompleteInstallApk();
|
||||
} else {
|
||||
downloadHandler.setProgressListener(this);
|
||||
|
||||
// Show the progress dialog, if for no other reason than to prevent them attempting
|
||||
// to download again (i.e. we force them to touch 'cancel' before they can access
|
||||
// the rest of the activity).
|
||||
Log.d(TAG, "Showing dialog to user after resuming app details view, because a download was previously in progress");
|
||||
updateProgressDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResumeFragments() {
|
||||
super.onResumeFragments();
|
||||
refreshApkList();
|
||||
refreshHeader();
|
||||
supportInvalidateOptionsMenu();
|
||||
if (downloadHandler != null) {
|
||||
if (downloadHandler.isComplete()) {
|
||||
downloadCompleteInstallApk();
|
||||
} else {
|
||||
localBroadcastManager.registerReceiver(downloaderProgressReceiver,
|
||||
new IntentFilter(Downloader.LOCAL_ACTION_PROGRESS));
|
||||
downloadHandler.setProgressListener(this);
|
||||
|
||||
if (downloadHandler.getTotalBytes() == 0)
|
||||
mHeaderFragment.startProgress();
|
||||
else
|
||||
mHeaderFragment.updateProgress(downloadHandler.getBytesRead(), downloadHandler.getTotalBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove progress listener, suppress progress dialog, set downloadHandler to null.
|
||||
* Remove progress listener, suppress progress bar, set downloadHandler to null.
|
||||
*/
|
||||
private void cleanUpFinishedDownload() {
|
||||
if (downloadHandler != null) {
|
||||
downloadHandler.removeProgressListener();
|
||||
removeProgressDialog();
|
||||
mHeaderFragment.removeProgress();
|
||||
downloadHandler = null;
|
||||
}
|
||||
}
|
||||
@ -485,7 +483,7 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
*/
|
||||
private void downloadCompleteInstallApk() {
|
||||
if (downloadHandler != null) {
|
||||
installApk(downloadHandler.localFile(), downloadHandler.getApk().id);
|
||||
installApk(downloadHandler.localFile());
|
||||
cleanUpFinishedDownload();
|
||||
}
|
||||
}
|
||||
@ -504,13 +502,23 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
setIgnoreUpdates(app.id, app.ignoreAllUpdates, app.ignoreThisUpdate);
|
||||
}
|
||||
|
||||
localBroadcastManager.unregisterReceiver(downloaderProgressReceiver);
|
||||
if (downloadHandler != null) {
|
||||
downloadHandler.removeProgressListener();
|
||||
}
|
||||
|
||||
removeProgressDialog();
|
||||
mHeaderFragment.removeProgress();
|
||||
}
|
||||
|
||||
private final BroadcastReceiver downloaderProgressReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (mHeaderFragment != null)
|
||||
mHeaderFragment.updateProgress(intent.getIntExtra(Downloader.EXTRA_BYTES_READ, -1),
|
||||
intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, -1));
|
||||
}
|
||||
};
|
||||
|
||||
private void onAppChanged() {
|
||||
if (!reset(app.id)) {
|
||||
AppDetails.this.finish();
|
||||
@ -553,13 +561,6 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void removeProgressDialog() {
|
||||
if (progressDialog != null) {
|
||||
progressDialog.dismiss();
|
||||
progressDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the display and list contents. Used when entering the activity, and
|
||||
// also when something has been installed/uninstalled.
|
||||
// Return true if the app was found, false otherwise.
|
||||
@ -619,9 +620,9 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
}
|
||||
|
||||
private void refreshHeader() {
|
||||
AppDetailsHeaderFragment headerFragment = (AppDetailsHeaderFragment)
|
||||
mHeaderFragment = (AppDetailsHeaderFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.header);
|
||||
headerFragment.refresh();
|
||||
mHeaderFragment.updateViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -725,8 +726,8 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(TextView widget, Spannable buffer,
|
||||
MotionEvent event) {
|
||||
public boolean onTouchEvent(@NonNull TextView widget, @NonNull Spannable buffer,
|
||||
@NonNull MotionEvent event) {
|
||||
try {
|
||||
return super.onTouchEvent(widget, buffer, event);
|
||||
} catch (ActivityNotFoundException ex) {
|
||||
@ -805,6 +806,10 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
// Install the version of this app denoted by 'app.curApk'.
|
||||
@Override
|
||||
public void install(final Apk apk) {
|
||||
// Ignore call if another download is running.
|
||||
if (downloadHandler != null && !downloadHandler.isComplete())
|
||||
return;
|
||||
|
||||
final String[] projection = { RepoProvider.DataColumns.ADDRESS };
|
||||
Repo repo = RepoProvider.Helper.findById(this, apk.repo, projection);
|
||||
if (repo == null || repo.address == null) {
|
||||
@ -854,32 +859,28 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
|
||||
private void startDownload(Apk apk, String repoAddress) {
|
||||
downloadHandler = new ApkDownloader(getBaseContext(), apk, repoAddress);
|
||||
localBroadcastManager.registerReceiver(downloaderProgressReceiver,
|
||||
new IntentFilter(Downloader.LOCAL_ACTION_PROGRESS));
|
||||
downloadHandler.setProgressListener(this);
|
||||
if (downloadHandler.download()) {
|
||||
updateProgressDialog();
|
||||
mHeaderFragment.startProgress();
|
||||
}
|
||||
}
|
||||
|
||||
private void installApk(File file, String packageName) {
|
||||
setSupportProgressBarIndeterminateVisibility(true);
|
||||
|
||||
private void installApk(File file) {
|
||||
try {
|
||||
installer.installPackage(file);
|
||||
} catch (AndroidNotCompatibleException e) {
|
||||
Log.e(TAG, "Android not compatible with this Installer!", e);
|
||||
setSupportProgressBarIndeterminateVisibility(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeApk(String packageName) {
|
||||
setSupportProgressBarIndeterminateVisibility(true);
|
||||
|
||||
try {
|
||||
installer.deletePackage(packageName);
|
||||
} catch (AndroidNotCompatibleException e) {
|
||||
Log.e(TAG, "Android not compatible with this Installer!", e);
|
||||
setSupportProgressBarIndeterminateVisibility(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -894,7 +895,6 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
PackageManagerCompat.setInstaller(mPm, app.id);
|
||||
}
|
||||
|
||||
setSupportProgressBarIndeterminateVisibility(false);
|
||||
onAppChanged();
|
||||
}
|
||||
});
|
||||
@ -906,7 +906,6 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSupportProgressBarIndeterminateVisibility(false);
|
||||
onAppChanged();
|
||||
}
|
||||
});
|
||||
@ -914,7 +913,6 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSupportProgressBarIndeterminateVisibility(false);
|
||||
onAppChanged();
|
||||
|
||||
Log.e(TAG, "Installer aborted with errorCode: " + errorCode);
|
||||
@ -945,79 +943,6 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
startActivity(Intent.createChooser(shareIntent, getString(R.string.menu_share)));
|
||||
}
|
||||
|
||||
private ProgressDialog getProgressDialog(String file) {
|
||||
if (progressDialog == null) {
|
||||
final ProgressDialog pd = new ProgressDialog(this);
|
||||
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
pd.setProgressNumberFormat("%1d/%2d KiB");
|
||||
}
|
||||
pd.setMessage(getString(R.string.download_server) + ":\n " + file);
|
||||
pd.setCancelable(true);
|
||||
pd.setCanceledOnTouchOutside(false);
|
||||
|
||||
// The indeterminate-ness will get overridden on the first progress event we receive.
|
||||
pd.setIndeterminate(true);
|
||||
|
||||
pd.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
Log.d(TAG, "User clicked 'cancel' on download, attempting to interrupt download thread.");
|
||||
if (downloadHandler != null) {
|
||||
downloadHandler.cancel();
|
||||
cleanUpFinishedDownload();
|
||||
} else {
|
||||
Log.e(TAG, "Tried to cancel, but the downloadHandler doesn't exist.");
|
||||
}
|
||||
progressDialog = null;
|
||||
Toast.makeText(AppDetails.this, getString(R.string.download_cancelled), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
pd.setButton(DialogInterface.BUTTON_NEUTRAL,
|
||||
getString(R.string.cancel),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
pd.cancel();
|
||||
}
|
||||
}
|
||||
);
|
||||
progressDialog = pd;
|
||||
}
|
||||
return progressDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks at the current <code>downloadHandler</code> and finds it's size and progress.
|
||||
* This is in comparison to {@link org.fdroid.fdroid.AppDetails#updateProgressDialog(int, int)},
|
||||
* which is used when you have the details from a freshly received
|
||||
* {@link org.fdroid.fdroid.ProgressListener.Event}.
|
||||
*/
|
||||
private void updateProgressDialog() {
|
||||
if (downloadHandler != null) {
|
||||
updateProgressDialog(downloadHandler.getProgress(), downloadHandler.getTotalSize());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateProgressDialog(int progress, int total) {
|
||||
if (downloadHandler != null) {
|
||||
ProgressDialog pd = getProgressDialog(downloadHandler.getRemoteAddress());
|
||||
if (total > 0) {
|
||||
pd.setIndeterminate(false);
|
||||
pd.setProgress(progress/1024);
|
||||
pd.setMax(total/1024);
|
||||
} else {
|
||||
pd.setIndeterminate(true);
|
||||
pd.setProgress(progress/1024);
|
||||
pd.setMax(0);
|
||||
}
|
||||
if (!pd.isShowing()) {
|
||||
Log.d(TAG, "Showing progress dialog for download.");
|
||||
pd.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(Event event) {
|
||||
if (downloadHandler == null || !downloadHandler.isEventFromThis(event)) {
|
||||
@ -1034,9 +959,6 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
|
||||
boolean finished = false;
|
||||
switch (event.type) {
|
||||
case Downloader.EVENT_PROGRESS:
|
||||
updateProgressDialog(event.progress, event.total);
|
||||
break;
|
||||
case ApkDownloader.EVENT_ERROR:
|
||||
final String text;
|
||||
if (event.getData().getInt(ApkDownloader.EVENT_DATA_ERROR_TYPE) == ApkDownloader.ERROR_HASH_MISMATCH)
|
||||
@ -1054,7 +976,8 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
removeProgressDialog();
|
||||
if (mHeaderFragment != null)
|
||||
mHeaderFragment.removeProgress();
|
||||
downloadHandler = null;
|
||||
}
|
||||
}
|
||||
@ -1447,9 +1370,14 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
}
|
||||
}
|
||||
|
||||
public static class AppDetailsHeaderFragment extends Fragment {
|
||||
public static class AppDetailsHeaderFragment extends Fragment implements View.OnClickListener {
|
||||
|
||||
private AppDetailsData data;
|
||||
private Button btMain;
|
||||
private ProgressBar progressBar;
|
||||
private TextView progressSize;
|
||||
private TextView progressPercent;
|
||||
private ImageButton cancelButton;
|
||||
protected final DisplayImageOptions displayImageOptions;
|
||||
public static boolean installed = false;
|
||||
public static boolean updateWanted = false;
|
||||
@ -1493,35 +1421,120 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
TextView tv = (TextView) view.findViewById(R.id.title);
|
||||
tv.setText(getApp().name);
|
||||
|
||||
btMain = (Button) view.findViewById(R.id.btn_main);
|
||||
progressBar = (ProgressBar) view.findViewById(R.id.progress_bar);
|
||||
progressSize = (TextView) view.findViewById(R.id.progress_size);
|
||||
progressPercent = (TextView) view.findViewById(R.id.progress_percentage);
|
||||
cancelButton = (ImageButton) view.findViewById(R.id.cancel);
|
||||
progressBar.setIndeterminate(false);
|
||||
cancelButton.setOnClickListener(this);
|
||||
|
||||
updateViews(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
refresh();
|
||||
updateViews();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
/**
|
||||
* Displays empty, indeterminate progress bar and related views.
|
||||
*/
|
||||
public void startProgress() {
|
||||
setProgressVisible(true);
|
||||
progressBar.setIndeterminate(true);
|
||||
progressSize.setText("");
|
||||
progressPercent.setText("");
|
||||
updateViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates progress bar and captions to new values (in bytes).
|
||||
*/
|
||||
public void updateProgress(long progress, long total) {
|
||||
long percent = progress * 100 / total;
|
||||
setProgressVisible(true);
|
||||
progressBar.setIndeterminate(false);
|
||||
progressBar.setProgress((int) percent);
|
||||
progressBar.setMax(100);
|
||||
progressSize.setText(readableFileSize(progress) + " / " + readableFileSize(total));
|
||||
progressPercent.setText(Long.toString(percent) + " %");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a number of bytes to a human readable file size (eg 3.5 GiB).
|
||||
*
|
||||
* Based on http://stackoverflow.com/a/5599842
|
||||
*/
|
||||
public String readableFileSize(long bytes) {
|
||||
final String[] units = getResources().getStringArray(R.array.file_size_units);
|
||||
if (bytes <= 0) return "0 " + units[0];
|
||||
int digitGroups = (int) (Math.log10(bytes) / Math.log10(1024));
|
||||
return new DecimalFormat("#,##0.#")
|
||||
.format(bytes / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows or hides progress bar and related views.
|
||||
*/
|
||||
private void setProgressVisible(boolean visible) {
|
||||
int state = (visible) ? View.VISIBLE : View.GONE;
|
||||
progressBar.setVisibility(state);
|
||||
progressSize.setVisibility(state);
|
||||
progressPercent.setVisibility(state);
|
||||
cancelButton.setVisibility(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes progress bar and related views, invokes {@link #updateViews()}.
|
||||
*/
|
||||
public void removeProgress() {
|
||||
setProgressVisible(false);
|
||||
updateViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels download and hides progress bar.
|
||||
*/
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
AppDetails activity = (AppDetails) getActivity();
|
||||
if (activity == null || activity.downloadHandler == null)
|
||||
return;
|
||||
|
||||
activity.downloadHandler.cancel();
|
||||
activity.cleanUpFinishedDownload();
|
||||
setProgressVisible(false);
|
||||
updateViews();
|
||||
}
|
||||
|
||||
public void updateViews() {
|
||||
updateViews(getView());
|
||||
}
|
||||
|
||||
public void updateViews(View view) {
|
||||
TextView statusView = (TextView) view.findViewById(R.id.status);
|
||||
Button btMain = (Button) view.findViewById(R.id.btn_main);
|
||||
btMain.setVisibility(View.VISIBLE);
|
||||
|
||||
AppDetails activity = (AppDetails) getActivity();
|
||||
if (activity.downloadHandler != null) {
|
||||
btMain.setText(R.string.downloading);
|
||||
btMain.setEnabled(false);
|
||||
}
|
||||
/*
|
||||
Check count > 0 due to incompatible apps resulting in an empty list.
|
||||
If App isn't installed
|
||||
*/
|
||||
if (!getApp().isInstalled() && getApp().suggestedVercode > 0 && ((AppDetails)getActivity()).adapter.getCount() > 0) {
|
||||
else if (!getApp().isInstalled() && getApp().suggestedVercode > 0 &&
|
||||
((AppDetails)getActivity()).adapter.getCount() > 0) {
|
||||
installed = false;
|
||||
statusView.setText(getString(R.string.details_notinstalled));
|
||||
NfcHelper.disableAndroidBeam(getActivity());
|
||||
// Set Install button and hide second button
|
||||
btMain.setText(R.string.menu_install);
|
||||
btMain.setOnClickListener(mOnClickListener);
|
||||
btMain.setEnabled(true);
|
||||
}
|
||||
// If App is installed
|
||||
else if (getApp().isInstalled()) {
|
||||
@ -1541,6 +1554,7 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
}
|
||||
}
|
||||
btMain.setOnClickListener(mOnClickListener);
|
||||
btMain.setEnabled(true);
|
||||
}
|
||||
TextView currentVersion = (TextView) view.findViewById(R.id.current_version);
|
||||
if (!getApks().isEmpty()) {
|
||||
@ -1574,6 +1588,8 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
|
||||
// If not installed, install
|
||||
else if (getApp().suggestedVercode > 0) {
|
||||
btMain.setEnabled(false);
|
||||
btMain.setText(R.string.system_install_installing);
|
||||
final Apk apkToInstall = ApkProvider.Helper.find(getActivity(), getApp().id, getApp().suggestedVercode);
|
||||
((AppDetails)getActivity()).install(apkToInstall);
|
||||
}
|
||||
|
@ -55,7 +55,6 @@ public class FDroid extends ActionBarActivity {
|
||||
|
||||
private static final String TAG = "FDroid";
|
||||
|
||||
public static final int REQUEST_MANAGEREPOS = 0;
|
||||
public static final int REQUEST_PREFS = 1;
|
||||
public static final int REQUEST_ENABLE_BLUETOOTH = 2;
|
||||
public static final int REQUEST_SWAP = 3;
|
||||
@ -244,12 +243,11 @@ public class FDroid extends ActionBarActivity {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case R.id.action_update_repo:
|
||||
updateRepos();
|
||||
UpdateService.updateNow(this);
|
||||
return true;
|
||||
|
||||
case R.id.action_manage_repos:
|
||||
Intent i = new Intent(this, ManageReposActivity.class);
|
||||
startActivityForResult(i, REQUEST_MANAGEREPOS);
|
||||
startActivity(new Intent(this, ManageReposActivity.class));
|
||||
return true;
|
||||
|
||||
case R.id.action_settings:
|
||||
@ -318,31 +316,6 @@ public class FDroid extends ActionBarActivity {
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_MANAGEREPOS:
|
||||
if (data != null && data.hasExtra(ManageReposActivity.REQUEST_UPDATE)) {
|
||||
AlertDialog.Builder ask_alrt = new AlertDialog.Builder(this);
|
||||
ask_alrt.setTitle(getString(R.string.repo_update_title));
|
||||
ask_alrt.setMessage(getString(R.string.repo_alrt));
|
||||
ask_alrt.setPositiveButton(getString(R.string.yes),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int whichButton) {
|
||||
updateRepos();
|
||||
}
|
||||
});
|
||||
ask_alrt.setNegativeButton(getString(R.string.no),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int whichButton) {
|
||||
// do nothing
|
||||
}
|
||||
});
|
||||
AlertDialog alert = ask_alrt.create();
|
||||
alert.show();
|
||||
}
|
||||
break;
|
||||
case REQUEST_PREFS:
|
||||
// The automatic update settings may have changed, so reschedule (or
|
||||
// unschedule) the service accordingly. It's cheap, so no need to
|
||||
@ -377,13 +350,6 @@ public class FDroid extends ActionBarActivity {
|
||||
});
|
||||
}
|
||||
|
||||
// Force a repo update now. A progress dialog is shown and the UpdateService
|
||||
// is told to do the update, which will result in the database changing. The
|
||||
// UpdateReceiver class should get told when this is finished.
|
||||
public void updateRepos() {
|
||||
UpdateService.updateNow(this);
|
||||
}
|
||||
|
||||
private TabManager getTabManager() {
|
||||
if (tabManager == null) {
|
||||
tabManager = new TabManager(this, viewPager);
|
||||
|
@ -3,7 +3,6 @@ package org.fdroid.fdroid;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
@ -22,6 +21,8 @@ import org.xml.sax.XMLReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.CodeSigner;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
@ -52,16 +53,14 @@ public class RepoUpdater {
|
||||
|
||||
/**
|
||||
* Updates an app repo as read out of the database into a {@link Repo} instance.
|
||||
*
|
||||
* @param context
|
||||
* @param repo a {@link Repo} read out of the local database
|
||||
* @param repo A {@link Repo} read out of the local database
|
||||
*/
|
||||
public RepoUpdater(@NonNull Context context, @NonNull Repo repo) {
|
||||
this.context = context;
|
||||
this.repo = repo;
|
||||
}
|
||||
|
||||
public void setProgressListener(ProgressListener progressListener) {
|
||||
public void setProgressListener(@Nullable ProgressListener progressListener) {
|
||||
this.progressListener = progressListener;
|
||||
}
|
||||
|
||||
@ -71,29 +70,23 @@ public class RepoUpdater {
|
||||
|
||||
public List<Apk> getApks() { return apks; }
|
||||
|
||||
protected String getIndexAddress() {
|
||||
protected URL getIndexAddress() throws MalformedURLException {
|
||||
String urlString = repo.address + "/index.jar";
|
||||
try {
|
||||
String versionName = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
|
||||
return repo.address + "/index.jar?client_version=" + versionName;
|
||||
urlString += "?client_version=" + versionName;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return repo.address + "/index.jar";
|
||||
return new URL(urlString);
|
||||
}
|
||||
|
||||
Downloader downloadIndex() throws UpdateException {
|
||||
Downloader downloader = null;
|
||||
try {
|
||||
downloader = DownloaderFactory.create(
|
||||
downloader = DownloaderFactory.create(context,
|
||||
getIndexAddress(), File.createTempFile("index-", "-downloaded", context.getCacheDir()));
|
||||
downloader.setCacheTag(repo.lastetag);
|
||||
|
||||
if (progressListener != null) { // interactive session, show progress
|
||||
Bundle data = new Bundle(1);
|
||||
data.putString(PROGRESS_DATA_REPO_ADDRESS, repo.address);
|
||||
downloader.setProgressListener(progressListener, data);
|
||||
}
|
||||
|
||||
downloader.downloadUninterrupted();
|
||||
|
||||
if (downloader.isCached()) {
|
||||
|
@ -22,11 +22,12 @@ import android.app.AlarmManager;
|
||||
import android.app.IntentService;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.OperationApplicationException;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
@ -34,14 +35,12 @@ import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.SystemClock;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
@ -63,9 +62,13 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
|
||||
private static final String TAG = "UpdateService";
|
||||
|
||||
public static final String RESULT_MESSAGE = "msg";
|
||||
public static final String RESULT_EVENT = "event";
|
||||
public static final String RESULT_REPO_ERRORS = "repoErrors";
|
||||
public static final String LOCAL_ACTION_STATUS = "status";
|
||||
|
||||
public static final String EXTRA_MESSAGE = "msg";
|
||||
public static final String EXTRA_REPO_ERRORS = "repoErrors";
|
||||
public static final String EXTRA_STATUS_CODE = "status";
|
||||
public static final String EXTRA_ADDRESS = "address";
|
||||
public static final String EXTRA_MANUAL_UPDATE = "manualUpdate";
|
||||
|
||||
public static final int STATUS_COMPLETE_WITH_CHANGES = 0;
|
||||
public static final int STATUS_COMPLETE_AND_SAME = 1;
|
||||
@ -74,19 +77,13 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
public static final int STATUS_ERROR_LOCAL_SMALL = 4;
|
||||
public static final int STATUS_INFO = 5;
|
||||
|
||||
// I don't like that I've had to dupliacte the statuses above with strings here, however
|
||||
// one method of communication/notification is using ResultReceiver (int status codes)
|
||||
// while the other uses progress events (string event types).
|
||||
public static final String EVENT_COMPLETE_WITH_CHANGES = "repoUpdateComplete (changed)";
|
||||
public static final String EVENT_COMPLETE_AND_SAME = "repoUpdateComplete (not changed)";
|
||||
public static final String EVENT_FINISHED = "repoUpdateFinished";
|
||||
public static final String EVENT_ERROR = "repoUpdateError";
|
||||
public static final String EVENT_INFO = "repoUpdateInfo";
|
||||
private LocalBroadcastManager localBroadcastManager;
|
||||
|
||||
public static final String EXTRA_RECEIVER = "receiver";
|
||||
public static final String EXTRA_ADDRESS = "address";
|
||||
private static final int NOTIFY_ID_UPDATING = 0;
|
||||
private static final int NOTIFY_ID_UPDATES_AVAILABLE = 1;
|
||||
|
||||
private ResultReceiver receiver = null;
|
||||
private NotificationManager notificationManager;
|
||||
private NotificationCompat.Builder notificationBuilder;
|
||||
|
||||
public UpdateService() {
|
||||
super("UpdateService");
|
||||
@ -106,130 +103,17 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
AppProvider.DataColumns.IGNORE_THISUPDATE
|
||||
};
|
||||
|
||||
// For receiving results from the UpdateService when we've told it to
|
||||
// update in response to a user request.
|
||||
public static class UpdateReceiver extends ResultReceiver {
|
||||
|
||||
private Context context;
|
||||
private ProgressDialog dialog;
|
||||
private ProgressListener listener;
|
||||
private String lastShownMessage = null;
|
||||
|
||||
public UpdateReceiver(Handler handler) {
|
||||
super(handler);
|
||||
public static void updateNow(Context context) {
|
||||
updateRepoNow(null, context);
|
||||
}
|
||||
|
||||
public UpdateReceiver setDialog(ProgressDialog dialog) {
|
||||
this.dialog = dialog;
|
||||
return this;
|
||||
}
|
||||
|
||||
public UpdateReceiver setListener(ProgressListener listener) {
|
||||
this.listener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void forwardEvent(String type) {
|
||||
if (listener != null) {
|
||||
listener.onProgress(new Event(type));
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureDialog() {
|
||||
if (dialog == null) {
|
||||
String title = context.getString(R.string.process_wait_title);
|
||||
String message = lastShownMessage == null ? context.getString(R.string.process_update_msg) : lastShownMessage;
|
||||
dialog = ProgressDialog.show(context, title, message, true, true);
|
||||
dialog.setIcon(android.R.drawable.ic_dialog_info);
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
}
|
||||
}
|
||||
|
||||
public UpdateReceiver showDialog(Context context) {
|
||||
this.context = context;
|
||||
ensureDialog();
|
||||
dialog.show();
|
||||
return this;
|
||||
}
|
||||
|
||||
public void hideDialog() {
|
||||
dialog.hide();
|
||||
dialog.dismiss();
|
||||
dialog = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
final String message = resultData.getString(RESULT_MESSAGE);
|
||||
boolean finished = false;
|
||||
switch (resultCode) {
|
||||
case STATUS_ERROR_GLOBAL:
|
||||
forwardEvent(EVENT_ERROR);
|
||||
Toast.makeText(context, context.getString(R.string.global_error_updating_repos) + " " + message, Toast.LENGTH_LONG).show();
|
||||
finished = true;
|
||||
break;
|
||||
case STATUS_ERROR_LOCAL:
|
||||
case STATUS_ERROR_LOCAL_SMALL:
|
||||
StringBuilder msgB = new StringBuilder();
|
||||
List<CharSequence> repoErrors = resultData.getCharSequenceArrayList(RESULT_REPO_ERRORS);
|
||||
for (CharSequence error : repoErrors) {
|
||||
if (msgB.length() > 0) msgB.append('\n');
|
||||
msgB.append(error);
|
||||
}
|
||||
if (resultCode == STATUS_ERROR_LOCAL_SMALL) {
|
||||
msgB.append('\n').append(context.getString(R.string.all_other_repos_fine));
|
||||
}
|
||||
Toast.makeText(context, msgB.toString(), Toast.LENGTH_LONG).show();
|
||||
finished = true;
|
||||
break;
|
||||
case STATUS_COMPLETE_WITH_CHANGES:
|
||||
forwardEvent(EVENT_COMPLETE_WITH_CHANGES);
|
||||
finished = true;
|
||||
break;
|
||||
case STATUS_COMPLETE_AND_SAME:
|
||||
forwardEvent(EVENT_COMPLETE_AND_SAME);
|
||||
Toast.makeText(context, context.getString(R.string.repos_unchanged), Toast.LENGTH_LONG).show();
|
||||
finished = true;
|
||||
break;
|
||||
case STATUS_INFO:
|
||||
forwardEvent(EVENT_INFO);
|
||||
if (dialog != null) {
|
||||
lastShownMessage = message;
|
||||
dialog.setMessage(message);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
forwardEvent(EVENT_FINISHED);
|
||||
if (dialog != null && dialog.isShowing()) {
|
||||
try {
|
||||
dialog.dismiss();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// sometimes dialog.isShowing() doesn't work :(
|
||||
// https://stackoverflow.com/questions/19538282/view-not-attached-to-window-manager-dialog-dismiss
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static UpdateReceiver updateNow(Context context) {
|
||||
return updateRepoNow(null, context);
|
||||
}
|
||||
|
||||
public static UpdateReceiver updateRepoNow(String address, Context context) {
|
||||
public static void updateRepoNow(String address, Context context) {
|
||||
Intent intent = new Intent(context, UpdateService.class);
|
||||
UpdateReceiver receiver = new UpdateReceiver(new Handler());
|
||||
receiver.showDialog(context);
|
||||
intent.putExtra(EXTRA_RECEIVER, receiver);
|
||||
intent.putExtra(EXTRA_MANUAL_UPDATE, true);
|
||||
if (!TextUtils.isEmpty(address)) {
|
||||
intent.putExtra(EXTRA_ADDRESS, address);
|
||||
}
|
||||
context.startService(intent);
|
||||
|
||||
return receiver;
|
||||
}
|
||||
|
||||
// Schedule (or cancel schedule for) this service, according to the
|
||||
@ -259,36 +143,135 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
localBroadcastManager.registerReceiver(downloadProgressReceiver,
|
||||
new IntentFilter(Downloader.LOCAL_ACTION_PROGRESS));
|
||||
localBroadcastManager.registerReceiver(updateStatusReceiver,
|
||||
new IntentFilter(LOCAL_ACTION_STATUS));
|
||||
|
||||
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
notificationBuilder = new NotificationCompat.Builder(this)
|
||||
.setSmallIcon(R.drawable.ic_refresh_white)
|
||||
.setOngoing(true)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setContentTitle(getString(R.string.update_notification_title));
|
||||
notificationManager.notify(NOTIFY_ID_UPDATING, notificationBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
notificationManager.cancel(NOTIFY_ID_UPDATING);
|
||||
localBroadcastManager.unregisterReceiver(downloadProgressReceiver);
|
||||
localBroadcastManager.unregisterReceiver(updateStatusReceiver);
|
||||
}
|
||||
|
||||
protected void sendStatus(int statusCode) {
|
||||
sendStatus(statusCode, null);
|
||||
}
|
||||
|
||||
protected void sendStatus(int statusCode, String message) {
|
||||
if (receiver != null) {
|
||||
Bundle resultData = new Bundle();
|
||||
if (!TextUtils.isEmpty(message)) {
|
||||
resultData.putString(RESULT_MESSAGE, message);
|
||||
}
|
||||
receiver.send(statusCode, resultData);
|
||||
}
|
||||
Intent intent = new Intent(LOCAL_ACTION_STATUS);
|
||||
intent.putExtra(EXTRA_STATUS_CODE, statusCode);
|
||||
if (!TextUtils.isEmpty(message))
|
||||
intent.putExtra(EXTRA_MESSAGE, message);
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
||||
}
|
||||
|
||||
protected void sendRepoErrorStatus(int statusCode, ArrayList<CharSequence> repoErrors) {
|
||||
if (receiver != null) {
|
||||
Bundle resultData = new Bundle();
|
||||
resultData.putCharSequenceArrayList(RESULT_REPO_ERRORS, repoErrors);
|
||||
receiver.send(statusCode, resultData);
|
||||
}
|
||||
Intent intent = new Intent(LOCAL_ACTION_STATUS);
|
||||
intent.putExtra(EXTRA_STATUS_CODE, statusCode);
|
||||
intent.putExtra(EXTRA_REPO_ERRORS, repoErrors.toArray(new CharSequence[repoErrors.size()]));
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* We might be doing a scheduled run, or we might have been launched by the
|
||||
* app in response to a user's request. If we have a receiver, it's the
|
||||
* latter...
|
||||
*/
|
||||
private boolean isScheduledRun() {
|
||||
return receiver == null;
|
||||
private final BroadcastReceiver downloadProgressReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (TextUtils.isEmpty(action))
|
||||
return;
|
||||
|
||||
if (!action.equals(Downloader.LOCAL_ACTION_PROGRESS))
|
||||
return;
|
||||
|
||||
String repoAddress = intent.getStringExtra(Downloader.EXTRA_ADDRESS);
|
||||
int downloadedSize = intent.getIntExtra(Downloader.EXTRA_BYTES_READ, -1);
|
||||
int totalSize = intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, -1);
|
||||
int percent = (int) ((double) downloadedSize / totalSize * 100);
|
||||
sendStatus(STATUS_INFO,
|
||||
getString(R.string.status_download, repoAddress,
|
||||
Utils.getFriendlySize(downloadedSize),
|
||||
Utils.getFriendlySize(totalSize), percent));
|
||||
}
|
||||
};
|
||||
|
||||
// For receiving results from the UpdateService when we've told it to
|
||||
// update in response to a user request.
|
||||
private final BroadcastReceiver updateStatusReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (TextUtils.isEmpty(action))
|
||||
return;
|
||||
|
||||
if (!action.equals(LOCAL_ACTION_STATUS))
|
||||
return;
|
||||
|
||||
final String message = intent.getStringExtra(EXTRA_MESSAGE);
|
||||
int resultCode = intent.getIntExtra(EXTRA_STATUS_CODE, -1);
|
||||
|
||||
String text;
|
||||
switch (resultCode) {
|
||||
case STATUS_INFO:
|
||||
notificationBuilder.setContentText(message)
|
||||
.setProgress(0, 0, true)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE);
|
||||
notificationManager.notify(NOTIFY_ID_UPDATING, notificationBuilder.build());
|
||||
break;
|
||||
case STATUS_ERROR_GLOBAL:
|
||||
text = context.getString(R.string.global_error_updating_repos) + " " + message;
|
||||
notificationBuilder.setContentText(text)
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_alert);
|
||||
notificationManager.notify(NOTIFY_ID_UPDATING, notificationBuilder.build());
|
||||
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case STATUS_ERROR_LOCAL:
|
||||
case STATUS_ERROR_LOCAL_SMALL:
|
||||
StringBuilder msgBuilder = new StringBuilder();
|
||||
CharSequence[] repoErrors = intent.getCharSequenceArrayExtra(EXTRA_REPO_ERRORS);
|
||||
for (CharSequence error : repoErrors) {
|
||||
if (msgBuilder.length() > 0) msgBuilder.append('\n');
|
||||
msgBuilder.append(error);
|
||||
}
|
||||
if (resultCode == STATUS_ERROR_LOCAL_SMALL) {
|
||||
msgBuilder.append('\n').append(context.getString(R.string.all_other_repos_fine));
|
||||
}
|
||||
text = msgBuilder.toString();
|
||||
notificationBuilder.setContentText(text)
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info);
|
||||
notificationManager.notify(NOTIFY_ID_UPDATING, notificationBuilder.build());
|
||||
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case STATUS_COMPLETE_WITH_CHANGES:
|
||||
break;
|
||||
case STATUS_COMPLETE_AND_SAME:
|
||||
text = context.getString(R.string.repos_unchanged);
|
||||
notificationBuilder.setContentText(text)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE);
|
||||
notificationManager.notify(NOTIFY_ID_UPDATING, notificationBuilder.build());
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether it is time to run the scheduled update.
|
||||
@ -332,15 +315,14 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
|
||||
receiver = intent.getParcelableExtra(EXTRA_RECEIVER);
|
||||
String address = intent.getStringExtra(EXTRA_ADDRESS);
|
||||
boolean manualUpdate = intent.getBooleanExtra(EXTRA_MANUAL_UPDATE, false);
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
|
||||
|
||||
// See if it's time to actually do anything yet...
|
||||
if (!isScheduledRun()) {
|
||||
if (manualUpdate) {
|
||||
Log.d(TAG, "Unscheduled (manually requested) update");
|
||||
} else if (!verifyIsTimeForScheduledRun()) {
|
||||
return;
|
||||
@ -460,11 +442,6 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
"Exception during update processing:\n"
|
||||
+ Log.getStackTraceString(e));
|
||||
sendStatus(STATUS_ERROR_GLOBAL, e.getMessage());
|
||||
} finally {
|
||||
Log.d(TAG, "Update took "
|
||||
+ ((System.currentTimeMillis() - startTime) / 1000)
|
||||
+ " seconds.");
|
||||
receiver = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -557,8 +534,7 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
.setContentText(contentText)
|
||||
.setStyle(createNotificationBigStyle(hasUpdates));
|
||||
|
||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.notify(1, builder.build());
|
||||
notificationManager.notify(NOTIFY_ID_UPDATES_AVAILABLE, builder.build());
|
||||
}
|
||||
|
||||
private List<String> getKnownAppIds(List<App> apps) {
|
||||
@ -783,9 +759,6 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
String totalSize = Utils.getFriendlySize(event.total);
|
||||
int percent = (int) ((double) event.progress / event.total * 100);
|
||||
switch (event.type) {
|
||||
case Downloader.EVENT_PROGRESS:
|
||||
message = getString(R.string.status_download, repoAddress, downloadedSize, totalSize, percent);
|
||||
break;
|
||||
case RepoUpdater.PROGRESS_TYPE_PROCESS_XML:
|
||||
message = getString(R.string.status_processing_xml_percent, repoAddress, downloadedSize, totalSize, percent);
|
||||
break;
|
||||
|
@ -52,7 +52,6 @@ public class ApkDownloader implements AsyncDownloadWrapper.Listener {
|
||||
|
||||
public static final int ERROR_HASH_MISMATCH = 101;
|
||||
public static final int ERROR_DOWNLOAD_FAILED = 102;
|
||||
public static final int ERROR_UNKNOWN = 103;
|
||||
|
||||
private static final String EVENT_SOURCE_ID = "sourceId";
|
||||
private static long downloadIdCounter = 0;
|
||||
@ -63,14 +62,13 @@ public class ApkDownloader implements AsyncDownloadWrapper.Listener {
|
||||
public static final String EVENT_DATA_ERROR_TYPE = "apkDownloadErrorType";
|
||||
|
||||
@NonNull private final Apk curApk;
|
||||
@NonNull private final Context context;
|
||||
@NonNull private final String repoAddress;
|
||||
@NonNull private final SanitizedFile localFile;
|
||||
@NonNull private final SanitizedFile potentiallyCachedFile;
|
||||
|
||||
private ProgressListener listener;
|
||||
private AsyncDownloadWrapper dlWrapper = null;
|
||||
private int progress = 0;
|
||||
private int totalSize = 0;
|
||||
private boolean isComplete = false;
|
||||
|
||||
private final long id = ++downloadIdCounter;
|
||||
@ -84,6 +82,7 @@ public class ApkDownloader implements AsyncDownloadWrapper.Listener {
|
||||
}
|
||||
|
||||
public ApkDownloader(@NonNull final Context context, @NonNull final Apk apk, @NonNull final String repoAddress) {
|
||||
this.context = context;
|
||||
curApk = apk;
|
||||
this.repoAddress = repoAddress;
|
||||
localFile = new SanitizedFile(Utils.getApkDownloadDir(context), apk.apkName);
|
||||
@ -191,7 +190,7 @@ public class ApkDownloader implements AsyncDownloadWrapper.Listener {
|
||||
Log.d(TAG, "Downloading apk from " + remoteAddress + " to " + localFile);
|
||||
|
||||
try {
|
||||
Downloader downloader = DownloaderFactory.create(remoteAddress, localFile);
|
||||
Downloader downloader = DownloaderFactory.create(context, remoteAddress, localFile);
|
||||
dlWrapper = new AsyncDownloadWrapper(downloader, this);
|
||||
dlWrapper.download();
|
||||
return true;
|
||||
@ -214,14 +213,6 @@ public class ApkDownloader implements AsyncDownloadWrapper.Listener {
|
||||
}
|
||||
|
||||
private void sendProgressEvent(Event event) {
|
||||
switch (event.type) {
|
||||
case Downloader.EVENT_PROGRESS:
|
||||
// Keep a copy of these ourselves, so people can interrogate us for the
|
||||
// info (in addition to receiving events with the info).
|
||||
totalSize = event.total;
|
||||
progress = event.progress;
|
||||
break;
|
||||
}
|
||||
|
||||
event.getData().putLong(EVENT_SOURCE_ID, id);
|
||||
|
||||
@ -230,18 +221,6 @@ public class ApkDownloader implements AsyncDownloadWrapper.Listener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceiveTotalDownloadSize(int size) {
|
||||
// Do nothing...
|
||||
// Rather, we will obtain the total download size from the progress events
|
||||
// when they start coming through.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceiveCacheTag(String cacheTag) {
|
||||
// Do nothing...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onErrorDownloading(String localisedExceptionDetails) {
|
||||
Log.e(TAG, "Download failed: " + localisedExceptionDetails);
|
||||
@ -292,7 +271,7 @@ public class ApkDownloader implements AsyncDownloadWrapper.Listener {
|
||||
|
||||
public Apk getApk() { return curApk; }
|
||||
|
||||
public int getProgress() { return progress; }
|
||||
public int getBytesRead() { return dlWrapper != null ? dlWrapper.getBytesRead() : 0; }
|
||||
|
||||
public int getTotalSize() { return totalSize; }
|
||||
public int getTotalBytes() { return dlWrapper != null ? dlWrapper.getTotalBytes() : 0; }
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ public class AsyncDownloadWrapper extends Handler {
|
||||
|
||||
private static final String TAG = "AsyncDownloadWrapper";
|
||||
|
||||
private static final int MSG_PROGRESS = 1;
|
||||
private static final int MSG_DOWNLOAD_COMPLETE = 2;
|
||||
private static final int MSG_DOWNLOAD_CANCELLED = 3;
|
||||
private static final int MSG_ERROR = 4;
|
||||
@ -43,16 +42,6 @@ public class AsyncDownloadWrapper extends Handler {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void fetchTotalDownloadSize() {
|
||||
int size = downloader.totalDownloadSize();
|
||||
listener.onReceiveTotalDownloadSize(size);
|
||||
}
|
||||
|
||||
public void fetchCacheTag() {
|
||||
String cacheTag = downloader.getCacheTag();
|
||||
listener.onReceiveCacheTag(cacheTag);
|
||||
}
|
||||
|
||||
public void download() {
|
||||
downloadThread = new DownloadThread();
|
||||
downloadThread.start();
|
||||
@ -64,22 +53,6 @@ public class AsyncDownloadWrapper extends Handler {
|
||||
}
|
||||
}
|
||||
|
||||
public static class NotDownloadingException extends Exception {
|
||||
public NotDownloadingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void cancelDownload() throws NotDownloadingException {
|
||||
if (downloadThread == null) {
|
||||
throw new RuntimeException("Can't cancel download, it hasn't started yet.");
|
||||
} else if (!downloadThread.isAlive()) {
|
||||
throw new RuntimeException("Can't cancel download, it is already finished.");
|
||||
}
|
||||
|
||||
downloadThread.interrupt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives "messages" from the download thread, and passes them onto the
|
||||
* relevant {@link org.fdroid.fdroid.net.AsyncDownloadWrapper.Listener}
|
||||
@ -87,11 +60,6 @@ public class AsyncDownloadWrapper extends Handler {
|
||||
*/
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.arg1) {
|
||||
case MSG_PROGRESS:
|
||||
Bundle data = message.getData();
|
||||
ProgressListener.Event event = data.getParcelable(MSG_DATA);
|
||||
listener.onProgress(event);
|
||||
break;
|
||||
case MSG_DOWNLOAD_COMPLETE:
|
||||
listener.onDownloadComplete();
|
||||
break;
|
||||
@ -104,19 +72,24 @@ public class AsyncDownloadWrapper extends Handler {
|
||||
}
|
||||
}
|
||||
|
||||
public int getBytesRead() {
|
||||
return downloader.getBytesRead();
|
||||
}
|
||||
|
||||
public int getTotalBytes() {
|
||||
return downloader.getTotalBytes();
|
||||
}
|
||||
|
||||
public interface Listener extends ProgressListener {
|
||||
void onReceiveTotalDownloadSize(int size);
|
||||
void onReceiveCacheTag(String cacheTag);
|
||||
void onErrorDownloading(String localisedExceptionDetails);
|
||||
void onDownloadComplete();
|
||||
void onDownloadCancelled();
|
||||
}
|
||||
|
||||
private class DownloadThread extends Thread implements ProgressListener {
|
||||
private class DownloadThread extends Thread {
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
downloader.setProgressListener(this);
|
||||
downloader.download();
|
||||
sendMessage(MSG_DOWNLOAD_COMPLETE);
|
||||
} catch (InterruptedException e) {
|
||||
@ -137,16 +110,5 @@ public class AsyncDownloadWrapper extends Handler {
|
||||
message.arg1 = messageType;
|
||||
AsyncDownloadWrapper.this.sendMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(Event event) {
|
||||
Message message = new Message();
|
||||
Bundle data = new Bundle();
|
||||
data.putParcelable(MSG_DATA, event);
|
||||
message.setData(data);
|
||||
message.arg1 = MSG_PROGRESS;
|
||||
AsyncDownloadWrapper.this.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
package org.fdroid.fdroid.net;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.content.Intent;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.ProgressListener;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
|
||||
import java.io.File;
|
||||
@ -20,49 +19,31 @@ import java.net.URL;
|
||||
public abstract class Downloader {
|
||||
|
||||
private static final String TAG = "Downloader";
|
||||
private OutputStream outputStream;
|
||||
|
||||
private ProgressListener progressListener = null;
|
||||
private Bundle eventData = null;
|
||||
public static final String LOCAL_ACTION_PROGRESS = "Downloader.PROGRESS";
|
||||
|
||||
public static final String EXTRA_ADDRESS = "extraAddress";
|
||||
public static final String EXTRA_BYTES_READ = "extraBytesRead";
|
||||
public static final String EXTRA_TOTAL_BYTES = "extraTotalBytes";
|
||||
|
||||
private final OutputStream outputStream;
|
||||
|
||||
private final LocalBroadcastManager localBroadcastManager;
|
||||
private final File outputFile;
|
||||
|
||||
protected URL sourceUrl;
|
||||
protected final URL sourceUrl;
|
||||
protected String cacheTag = null;
|
||||
|
||||
public static final String EVENT_PROGRESS = "downloadProgress";
|
||||
protected int bytesRead = 0;
|
||||
protected int totalBytes = 0;
|
||||
|
||||
public abstract InputStream getInputStream() throws IOException;
|
||||
|
||||
// The context is required for opening the file to write to.
|
||||
Downloader(String destFile, @NonNull Context ctx)
|
||||
throws FileNotFoundException, MalformedURLException {
|
||||
this(new File(ctx.getFilesDir() + File.separator + destFile));
|
||||
}
|
||||
|
||||
// The context is required for opening the file to write to.
|
||||
Downloader(@NonNull Context ctx) throws IOException {
|
||||
this(File.createTempFile("dl-", "", ctx.getCacheDir()));
|
||||
}
|
||||
|
||||
Downloader(File destFile)
|
||||
Downloader(Context context, URL url, File destFile)
|
||||
throws FileNotFoundException, MalformedURLException {
|
||||
this.sourceUrl = url;
|
||||
outputFile = destFile;
|
||||
outputStream = new FileOutputStream(outputFile);
|
||||
}
|
||||
|
||||
Downloader(OutputStream output)
|
||||
throws MalformedURLException {
|
||||
outputStream = output;
|
||||
outputFile = null;
|
||||
}
|
||||
|
||||
public void setProgressListener(ProgressListener listener) {
|
||||
setProgressListener(listener, null);
|
||||
}
|
||||
|
||||
public void setProgressListener(ProgressListener listener, Bundle eventData) {
|
||||
this.progressListener = listener;
|
||||
this.eventData = eventData;
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(context);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -157,11 +138,16 @@ public abstract class Downloader {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This copies the downloaded data from the InputStream to the OutputStream,
|
||||
* keeping track of the number of bytes that have flowed through for the
|
||||
* progress counter.
|
||||
*/
|
||||
protected void copyInputToOutputStream(InputStream input) throws IOException, InterruptedException {
|
||||
|
||||
byte[] buffer = new byte[Utils.BUFFER_SIZE];
|
||||
int bytesRead = 0;
|
||||
int totalBytes = totalDownloadSize();
|
||||
this.totalBytes = totalDownloadSize();
|
||||
|
||||
// Getting the total download size could potentially take time, depending on how
|
||||
// it is implemented, so we may as well check this before we proceed.
|
||||
@ -185,13 +171,19 @@ public abstract class Downloader {
|
||||
}
|
||||
|
||||
protected void sendProgress(int bytesRead, int totalBytes) {
|
||||
sendProgress(new ProgressListener.Event(EVENT_PROGRESS, bytesRead, totalBytes, eventData));
|
||||
this.bytesRead = bytesRead;
|
||||
Intent intent = new Intent(LOCAL_ACTION_PROGRESS);
|
||||
intent.putExtra(EXTRA_ADDRESS, sourceUrl.toString());
|
||||
intent.putExtra(EXTRA_BYTES_READ, bytesRead);
|
||||
intent.putExtra(EXTRA_TOTAL_BYTES, totalBytes);
|
||||
localBroadcastManager.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
protected void sendProgress(ProgressListener.Event event) {
|
||||
if (progressListener != null) {
|
||||
progressListener.onProgress(event);
|
||||
}
|
||||
public int getBytesRead() {
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public int getTotalBytes() {
|
||||
return totalBytes;
|
||||
}
|
||||
}
|
||||
|
@ -4,26 +4,46 @@ import android.content.Context;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
public class DownloaderFactory {
|
||||
|
||||
public static Downloader create(String url, Context context)
|
||||
/**
|
||||
* Downloads to a temporary file, which *you must delete yourself when
|
||||
* you are done. It is stored in {@link Context#getCacheDir()} and starts
|
||||
* with the prefix {@code dl-}.
|
||||
*/
|
||||
public static Downloader create(Context context, String urlString)
|
||||
throws IOException {
|
||||
if (isOnionAddress(url)) {
|
||||
return new TorHttpDownloader(url, context);
|
||||
}
|
||||
return new HttpDownloader(url, context);
|
||||
return create(context, new URL(urlString));
|
||||
}
|
||||
|
||||
public static Downloader create(String url, File destFile)
|
||||
/**
|
||||
* Downloads to a temporary file, which *you must delete yourself when
|
||||
* you are done. It is stored in {@link Context#getCacheDir()} and starts
|
||||
* with the prefix {@code dl-}.
|
||||
*/
|
||||
public static Downloader create(Context context, URL url)
|
||||
throws IOException {
|
||||
if (isOnionAddress(url)) {
|
||||
return new TorHttpDownloader(url, destFile);
|
||||
}
|
||||
return new HttpDownloader(url, destFile);
|
||||
File destFile = File.createTempFile("dl-", "", context.getCacheDir());
|
||||
destFile.deleteOnExit(); // this probably does nothing, but maybe...
|
||||
return create(context, url, destFile);
|
||||
}
|
||||
|
||||
private static boolean isOnionAddress(String url) {
|
||||
return url.matches("^[a-zA-Z0-9]+://[^/]+\\.onion/.*");
|
||||
public static Downloader create(Context context, String urlString, File destFile)
|
||||
throws IOException {
|
||||
return create(context, new URL(urlString), destFile);
|
||||
}
|
||||
|
||||
public static Downloader create(Context context, URL url, File destFile)
|
||||
throws IOException {
|
||||
if (isOnionAddress(url)) {
|
||||
return new TorHttpDownloader(context, url, destFile);
|
||||
}
|
||||
return new HttpDownloader(context, url, destFile);
|
||||
}
|
||||
|
||||
private static boolean isOnionAddress(URL url) {
|
||||
return url.getHost().endsWith(".onion");
|
||||
}
|
||||
}
|
||||
|
@ -27,21 +27,9 @@ public class HttpDownloader extends Downloader {
|
||||
protected HttpURLConnection connection;
|
||||
private int statusCode = -1;
|
||||
|
||||
// The context is required for opening the file to write to.
|
||||
HttpDownloader(String source, File destFile)
|
||||
HttpDownloader(Context context, URL url, File destFile)
|
||||
throws FileNotFoundException, MalformedURLException {
|
||||
super(destFile);
|
||||
sourceUrl = new URL(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads to a temporary file, which *you must delete yourself when
|
||||
* you are done*.
|
||||
* @see org.fdroid.fdroid.net.Downloader#getFile()
|
||||
*/
|
||||
HttpDownloader(String source, Context ctx) throws IOException {
|
||||
super(ctx);
|
||||
sourceUrl = new URL(source);
|
||||
super(context, url, destFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -22,7 +22,7 @@ public class IconDownloader extends BaseImageDownloader {
|
||||
switch (Scheme.ofUri(imageUri)) {
|
||||
case HTTP:
|
||||
case HTTPS:
|
||||
Downloader downloader = DownloaderFactory.create(imageUri, context);
|
||||
Downloader downloader = DownloaderFactory.create(context, imageUri);
|
||||
return downloader.getInputStream();
|
||||
default:
|
||||
return super.getStream(imageUri, extra);
|
||||
|
@ -10,16 +10,13 @@ import java.net.InetSocketAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Proxy;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URL;
|
||||
|
||||
public class TorHttpDownloader extends HttpDownloader {
|
||||
|
||||
TorHttpDownloader(String url, Context ctx) throws IOException {
|
||||
super(url, ctx);
|
||||
}
|
||||
|
||||
TorHttpDownloader(String url, File destFile)
|
||||
TorHttpDownloader(Context context, URL url, File destFile)
|
||||
throws FileNotFoundException, MalformedURLException {
|
||||
super(url, destFile);
|
||||
super(context, url, destFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -64,7 +64,6 @@ import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.fdroid.fdroid.FDroid;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.ProgressListener;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.UpdateService;
|
||||
import org.fdroid.fdroid.compat.ClipboardCompat;
|
||||
@ -74,7 +73,6 @@ import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.net.MDnsHelper;
|
||||
import org.fdroid.fdroid.net.MDnsHelper.DiscoveredRepo;
|
||||
import org.fdroid.fdroid.net.MDnsHelper.RepoScanListAdapter;
|
||||
import org.fdroid.fdroid.views.fragments.RepoDetailsFragment;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
@ -87,13 +85,6 @@ import java.util.Locale;
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
public class ManageReposActivity extends ActionBarActivity {
|
||||
|
||||
/**
|
||||
* If we have a new repo added, or the address of a repo has changed, then
|
||||
* we when we're finished, we'll set this boolean to true in the intent that
|
||||
* we finish with, to signify that we want the main list of apps updated.
|
||||
*/
|
||||
public static final String REQUEST_UPDATE = "update";
|
||||
private static final String TAG = "ManageReposActivity";
|
||||
|
||||
private static final String DEFAULT_NEW_REPO_TEXT = "https://";
|
||||
@ -104,10 +95,6 @@ public class ManageReposActivity extends ActionBarActivity {
|
||||
IS_SWAP
|
||||
}
|
||||
|
||||
private UpdateService.UpdateReceiver updateHandler = null;
|
||||
|
||||
private static boolean changed = false;
|
||||
|
||||
private RepoListFragment listFragment;
|
||||
|
||||
/**
|
||||
@ -152,21 +139,11 @@ public class ManageReposActivity extends ActionBarActivity {
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (updateHandler != null) {
|
||||
updateHandler.showDialog(this);
|
||||
}
|
||||
|
||||
/* let's see if someone is trying to send us a new repo */
|
||||
addRepoFromIntent(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (updateHandler != null) {
|
||||
updateHandler.hideDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
setIntent(intent);
|
||||
@ -175,22 +152,10 @@ public class ManageReposActivity extends ActionBarActivity {
|
||||
@Override
|
||||
public void finish() {
|
||||
Intent ret = new Intent();
|
||||
markChangedIfRequired(ret);
|
||||
setResult(RESULT_OK, ret);
|
||||
super.finish();
|
||||
}
|
||||
|
||||
private boolean hasChanged() {
|
||||
return changed;
|
||||
}
|
||||
|
||||
private void markChangedIfRequired(Intent intent) {
|
||||
if (hasChanged()) {
|
||||
Log.i(TAG, "Repo details have changed, prompting for update.");
|
||||
intent.putExtra(REQUEST_UPDATE, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.manage_repos, menu);
|
||||
@ -202,7 +167,6 @@ public class ManageReposActivity extends ActionBarActivity {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
Intent destIntent = new Intent(this, FDroid.class);
|
||||
markChangedIfRequired(destIntent);
|
||||
setResult(RESULT_OK, destIntent);
|
||||
NavUtils.navigateUpTo(this, destIntent);
|
||||
return true;
|
||||
@ -210,7 +174,7 @@ public class ManageReposActivity extends ActionBarActivity {
|
||||
showAddRepo();
|
||||
return true;
|
||||
case R.id.action_update_repo:
|
||||
updateRepos();
|
||||
UpdateService.updateNow(this);
|
||||
return true;
|
||||
case R.id.action_find_local_repos:
|
||||
scanForRepos();
|
||||
@ -219,26 +183,6 @@ public class ManageReposActivity extends ActionBarActivity {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void updateRepos() {
|
||||
updateHandler = UpdateService.updateNow(this).setListener(
|
||||
new ProgressListener() {
|
||||
@Override
|
||||
public void onProgress(Event event) {
|
||||
switch (event.type) {
|
||||
case UpdateService.EVENT_COMPLETE_AND_SAME:
|
||||
case UpdateService.EVENT_COMPLETE_WITH_CHANGES:
|
||||
// No need to prompt to update any more, we just
|
||||
// did it!
|
||||
changed = false;
|
||||
break;
|
||||
case UpdateService.EVENT_FINISHED:
|
||||
updateHandler = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void scanForRepos() {
|
||||
final RepoScanListAdapter adapter = new RepoScanListAdapter(this);
|
||||
final MDnsHelper mDnsHelper = new MDnsHelper(this, adapter);
|
||||
@ -706,11 +650,11 @@ public class ManageReposActivity extends ActionBarActivity {
|
||||
|
||||
/**
|
||||
* If started by an intent that expects a result (e.g. QR codes) then we
|
||||
* will set a result and finish. Otherwise, we'll refresh the list of repos
|
||||
* will set a result and finish. Otherwise, we'll updateViews the list of repos
|
||||
* to reflect the newly created repo.
|
||||
*/
|
||||
private void finishedAddingRepo() {
|
||||
changed = true;
|
||||
UpdateService.updateNow(ManageReposActivity.this);
|
||||
if (addRepoDialog.isShowing()) {
|
||||
addRepoDialog.dismiss();
|
||||
}
|
||||
@ -783,7 +727,7 @@ public class ManageReposActivity extends ActionBarActivity {
|
||||
/**
|
||||
* NOTE: If somebody toggles a repo off then on again, it will have
|
||||
* removed all apps from the index when it was toggled off, so when it
|
||||
* is toggled on again, then it will require a refresh. Previously, I
|
||||
* is toggled on again, then it will require a updateViews. Previously, I
|
||||
* toyed with the idea of remembering whether they had toggled on or
|
||||
* off, and then only actually performing the function when the activity
|
||||
* stopped, but I think that will be problematic. What about when they
|
||||
@ -803,7 +747,7 @@ public class ManageReposActivity extends ActionBarActivity {
|
||||
RepoProvider.Helper.update(getActivity(), repo, values);
|
||||
|
||||
if (isEnabled) {
|
||||
changed = true;
|
||||
UpdateService.updateNow(getActivity());
|
||||
} else {
|
||||
FDroidApp app = (FDroidApp) getActivity().getApplication();
|
||||
RepoProvider.Helper.purgeApps(getActivity(), repo, app);
|
||||
@ -888,7 +832,7 @@ public class ManageReposActivity extends ActionBarActivity {
|
||||
|
||||
public void editRepo(Repo repo) {
|
||||
Intent intent = new Intent(getActivity(), RepoDetailsActivity.class);
|
||||
intent.putExtra(RepoDetailsFragment.ARG_REPO_ID, repo.getId());
|
||||
intent.putExtra(RepoDetailsActivity.ARG_REPO_ID, repo.getId());
|
||||
startActivityForResult(intent, SHOW_REPO_DETAILS);
|
||||
}
|
||||
|
||||
|
@ -1,33 +1,91 @@
|
||||
package org.fdroid.fdroid.views;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.Uri;
|
||||
import android.nfc.NdefMessage;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.LinearLayout;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.NfcHelper;
|
||||
import org.fdroid.fdroid.NfcNotEnabledActivity;
|
||||
import org.fdroid.fdroid.QrGenAsyncTask;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.UpdateService;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.views.fragments.RepoDetailsFragment;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class RepoDetailsActivity extends ActionBarActivity {
|
||||
private static final String TAG = "RepoDetailsActivity";
|
||||
|
||||
private Repo repo;
|
||||
public static final String MIME_TYPE = "application/vnd.org.fdroid.fdroid.repo";
|
||||
public static final String ARG_REPO_ID = "repo_id";
|
||||
|
||||
static final String MIME_TYPE = "application/vnd.org.fdroid.fdroid.repo";
|
||||
/**
|
||||
* If the repo has been updated at least once, then we will show
|
||||
* all of this info, otherwise they will be hidden.
|
||||
*/
|
||||
private static final int[] SHOW_IF_EXISTS = {
|
||||
R.id.label_repo_name,
|
||||
R.id.text_repo_name,
|
||||
R.id.label_description,
|
||||
R.id.text_description,
|
||||
R.id.label_num_apps,
|
||||
R.id.text_num_apps,
|
||||
R.id.label_last_update,
|
||||
R.id.text_last_update,
|
||||
R.id.label_repo_fingerprint,
|
||||
R.id.text_repo_fingerprint,
|
||||
R.id.text_repo_fingerprint_description
|
||||
};
|
||||
/**
|
||||
* If the repo has <em>not</em> been updated yet, then we only show
|
||||
* these, otherwise they are hidden.
|
||||
*/
|
||||
private static final int[] HIDE_IF_EXISTS = {
|
||||
R.id.text_not_yet_updated,
|
||||
R.id.btn_update
|
||||
};
|
||||
private Repo repo;
|
||||
private long repoId;
|
||||
private View repoView;
|
||||
|
||||
/**
|
||||
* Help function to make switching between two view states easier.
|
||||
* Perhaps there is a better way to do this. I recall that using Adobe
|
||||
* Flex, there was a thing called "ViewStates" for exactly this. Wonder if
|
||||
* that exists in Android?
|
||||
*/
|
||||
private static void setMultipleViewVisibility(View parent,
|
||||
int[] viewIds,
|
||||
int visibility) {
|
||||
for (int viewId : viewIds) {
|
||||
parent.findViewById(viewId).setVisibility(visibility);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -35,27 +93,11 @@ public class RepoDetailsActivity extends ActionBarActivity {
|
||||
((FDroidApp) getApplication()).applyTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
long repoId = getIntent().getLongExtra(RepoDetailsFragment.ARG_REPO_ID, 0);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
|
||||
// Need to set a dummy view (which will get overridden by the fragment manager
|
||||
// below) so that we can call setContentView(). This is a work around for
|
||||
// a (bug?) thing in 3.0, 3.1 which requires setContentView to be invoked before
|
||||
// the actionbar is played with:
|
||||
// http://blog.perpetumdesign.com/2011/08/strange-case-of-dr-action-and-mr-bar.html
|
||||
if (Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT <= 13) {
|
||||
setContentView(new LinearLayout(this));
|
||||
}
|
||||
|
||||
RepoDetailsFragment fragment = new RepoDetailsFragment();
|
||||
fragment.setArguments(getIntent().getExtras());
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(android.R.id.content, fragment)
|
||||
.commit();
|
||||
}
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
setContentView(R.layout.repodetails);
|
||||
repoView = findViewById(R.id.repoView);
|
||||
|
||||
repoId = getIntent().getLongExtra(ARG_REPO_ID, 0);
|
||||
final String[] projection = {
|
||||
RepoProvider.DataColumns.NAME,
|
||||
RepoProvider.DataColumns.ADDRESS,
|
||||
@ -63,8 +105,23 @@ public class RepoDetailsActivity extends ActionBarActivity {
|
||||
};
|
||||
repo = RepoProvider.Helper.findById(this, repoId, projection);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
setTitle(repo.getName());
|
||||
setTitle(repo.name);
|
||||
|
||||
TextView inputUrl = (TextView) findViewById(R.id.input_repo_url);
|
||||
inputUrl.setText(repo.address);
|
||||
|
||||
Button update = (Button) repoView.findViewById(R.id.btn_update);
|
||||
update.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
performUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
Uri uri = Uri.parse(repo.address);
|
||||
uri = uri.buildUpon().appendQueryParameter("fingerprint", repo.fingerprint).build();
|
||||
String qrUriString = uri.toString().toUpperCase(Locale.ENGLISH);
|
||||
new QrGenAsyncTask(this, R.id.qr_code).execute(uri.toString());
|
||||
}
|
||||
|
||||
@TargetApi(14)
|
||||
@ -82,6 +139,19 @@ public class RepoDetailsActivity extends ActionBarActivity {
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
/*
|
||||
* After, for example, a repo update, the details will have changed in the
|
||||
* database. However, or local reference to the Repo object will not
|
||||
* have been updated. The safest way to deal with this is to reload the
|
||||
* repo object directly from the database.
|
||||
*/
|
||||
repo = RepoProvider.Helper.findById(this, repoId);
|
||||
updateRepoView();
|
||||
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver,
|
||||
new IntentFilter(UpdateService.LOCAL_ACTION_STATUS));
|
||||
|
||||
// FDroid.java and AppDetails set different NFC actions, so reset here
|
||||
setNfc();
|
||||
processIntent(getIntent());
|
||||
@ -105,21 +175,189 @@ public class RepoDetailsActivity extends ActionBarActivity {
|
||||
Log.i(TAG, "Got this URL: " + url);
|
||||
Toast.makeText(this, "Got this URL: " + url, Toast.LENGTH_LONG).show();
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
String packageName = getPackageName();
|
||||
intent.setClassName(packageName, packageName + ".ManageRepo");
|
||||
intent.setClass(this, ManageReposActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
int statusCode = intent.getIntExtra(UpdateService.EXTRA_STATUS_CODE, -1);
|
||||
if (statusCode == UpdateService.STATUS_COMPLETE_WITH_CHANGES)
|
||||
updateRepoView();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.repo_details_activity, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
case R.id.menu_update:
|
||||
performUpdate();
|
||||
return true;
|
||||
case R.id.menu_delete:
|
||||
promptForDelete();
|
||||
return true;
|
||||
case R.id.menu_enable_nfc:
|
||||
Intent intent = new Intent(this, NfcNotEnabledActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
if (Build.VERSION.SDK_INT >= 14) {
|
||||
prepareNfcMenuItems(menu);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@TargetApi(16)
|
||||
private void prepareNfcMenuItems(Menu menu) {
|
||||
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||
if (nfcAdapter == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean needsEnableNfcMenuItem;
|
||||
if (Build.VERSION.SDK_INT < 16) {
|
||||
needsEnableNfcMenuItem = !nfcAdapter.isEnabled();
|
||||
} else {
|
||||
needsEnableNfcMenuItem = !nfcAdapter.isNdefPushEnabled();
|
||||
}
|
||||
|
||||
MenuItem menuItem = menu.findItem(R.id.menu_enable_nfc);
|
||||
menuItem.setVisible(needsEnableNfcMenuItem);
|
||||
}
|
||||
|
||||
private void setupDescription(View parent, Repo repo) {
|
||||
|
||||
TextView descriptionLabel = (TextView) parent.findViewById(R.id.label_description);
|
||||
TextView description = (TextView) parent.findViewById(R.id.text_description);
|
||||
|
||||
if (TextUtils.isEmpty(repo.description)) {
|
||||
descriptionLabel.setVisibility(View.GONE);
|
||||
description.setVisibility(View.GONE);
|
||||
description.setText("");
|
||||
} else {
|
||||
descriptionLabel.setVisibility(View.VISIBLE);
|
||||
description.setVisibility(View.VISIBLE);
|
||||
description.setText(repo.description.replaceAll("\n", " "));
|
||||
}
|
||||
}
|
||||
|
||||
private void setupRepoFingerprint(View parent, Repo repo) {
|
||||
TextView repoFingerprintView = (TextView) parent.findViewById(R.id.text_repo_fingerprint);
|
||||
TextView repoFingerprintDescView = (TextView) parent.findViewById(R.id.text_repo_fingerprint_description);
|
||||
|
||||
String repoFingerprint;
|
||||
int repoFingerprintColor;
|
||||
|
||||
// TODO show the current state of the signature check, not just whether there is a key or not
|
||||
if (TextUtils.isEmpty(repo.fingerprint) && TextUtils.isEmpty(repo.pubkey)) {
|
||||
repoFingerprint = getResources().getString(R.string.unsigned);
|
||||
repoFingerprintColor = getResources().getColor(R.color.unsigned);
|
||||
repoFingerprintDescView.setVisibility(View.VISIBLE);
|
||||
repoFingerprintDescView.setText(getResources().getString(R.string.unsigned_description));
|
||||
} else {
|
||||
// this is based on repo.fingerprint always existing, which it should
|
||||
repoFingerprint = Utils.formatFingerprint(this, repo.fingerprint);
|
||||
repoFingerprintColor = getResources().getColor(R.color.signed);
|
||||
repoFingerprintDescView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
repoFingerprintView.setText(repoFingerprint);
|
||||
repoFingerprintView.setTextColor(repoFingerprintColor);
|
||||
}
|
||||
|
||||
private void updateRepoView() {
|
||||
|
||||
if (repo.hasBeenUpdated()) {
|
||||
updateViewForExistingRepo(repoView);
|
||||
} else {
|
||||
updateViewForNewRepo(repoView);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void updateViewForNewRepo(View repoView) {
|
||||
setMultipleViewVisibility(repoView, HIDE_IF_EXISTS, View.VISIBLE);
|
||||
setMultipleViewVisibility(repoView, SHOW_IF_EXISTS, View.GONE);
|
||||
}
|
||||
|
||||
private void updateViewForExistingRepo(View repoView) {
|
||||
setMultipleViewVisibility(repoView, SHOW_IF_EXISTS, View.VISIBLE);
|
||||
setMultipleViewVisibility(repoView, HIDE_IF_EXISTS, View.GONE);
|
||||
|
||||
TextView name = (TextView) repoView.findViewById(R.id.text_repo_name);
|
||||
TextView numApps = (TextView) repoView.findViewById(R.id.text_num_apps);
|
||||
TextView lastUpdated = (TextView) repoView.findViewById(R.id.text_last_update);
|
||||
|
||||
name.setText(repo.name);
|
||||
|
||||
int appCount = RepoProvider.Helper.countAppsForRepo(this, repoId);
|
||||
numApps.setText(Integer.toString(appCount));
|
||||
|
||||
setupDescription(repoView, repo);
|
||||
setupRepoFingerprint(repoView, repo);
|
||||
|
||||
// Repos that existed before this feature was supported will have an
|
||||
// "Unknown" last update until next time they update...
|
||||
String lastUpdate = repo.lastUpdated != null
|
||||
? repo.lastUpdated.toString() : getString(R.string.unknown);
|
||||
lastUpdated.setText(lastUpdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* When an update is performed, notify the listener so that the repo
|
||||
* list can be updated. We will perform the update ourselves though.
|
||||
*/
|
||||
private void performUpdate() {
|
||||
// Ensure repo is enabled before updating...
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(RepoProvider.DataColumns.IN_USE, 1);
|
||||
RepoProvider.Helper.update(this, repo, values);
|
||||
|
||||
UpdateService.updateRepoNow(repo.address, this);
|
||||
}
|
||||
|
||||
private void promptForDelete() {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.repo_confirm_delete_title)
|
||||
.setMessage(R.string.repo_confirm_delete_body)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
RepoProvider.Helper.remove(getApplicationContext(), repoId);
|
||||
finish();
|
||||
}
|
||||
}).setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// Do nothing...
|
||||
}
|
||||
}
|
||||
).show();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,392 +0,0 @@
|
||||
package org.fdroid.fdroid.views.fragments;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.ContentValues;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.fdroid.fdroid.NfcNotEnabledActivity;
|
||||
import org.fdroid.fdroid.ProgressListener;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.UpdateService;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
|
||||
public class RepoDetailsFragment extends Fragment {
|
||||
|
||||
public static final String ARG_REPO_ID = "repo_id";
|
||||
|
||||
/**
|
||||
* If the repo has been updated at least once, then we will show
|
||||
* all of this info, otherwise they will be hidden.
|
||||
*/
|
||||
private static final int[] SHOW_IF_EXISTS = {
|
||||
R.id.label_repo_name,
|
||||
R.id.text_repo_name,
|
||||
R.id.label_description,
|
||||
R.id.text_description,
|
||||
R.id.label_num_apps,
|
||||
R.id.text_num_apps,
|
||||
R.id.label_last_update,
|
||||
R.id.text_last_update,
|
||||
R.id.label_repo_fingerprint,
|
||||
R.id.text_repo_fingerprint,
|
||||
R.id.text_repo_fingerprint_description
|
||||
};
|
||||
|
||||
/**
|
||||
* If the repo has <em>not</em> been updated yet, then we only show
|
||||
* these, otherwise they are hidden.
|
||||
*/
|
||||
private static final int[] HIDE_IF_EXISTS = {
|
||||
R.id.text_not_yet_updated,
|
||||
R.id.btn_update
|
||||
};
|
||||
|
||||
private static final int DELETE = 0;
|
||||
private static final int UPDATE = 1;
|
||||
private static final int ENABLE_NFC = 2;
|
||||
|
||||
private static final String TAG = "RepoDetailsFragment";
|
||||
|
||||
private MenuItem enableNfc = null;
|
||||
private UpdateService.UpdateReceiver updateHandler = null;
|
||||
|
||||
// TODO: Currently initialised in onCreateView. Not sure if that is the
|
||||
// best way to go about this...
|
||||
private Repo repo;
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
if (updateHandler != null) {
|
||||
updateHandler.showDialog(getActivity());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
if (updateHandler != null) {
|
||||
updateHandler.hideDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private long getRepoId() {
|
||||
return getArguments().getLong(RepoDetailsFragment.ARG_REPO_ID, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* After, for example, a repo update, the details will have changed in the
|
||||
* database. However, or local reference to the Repo object will not
|
||||
* have been updated. The safest way to deal with this is to reload the
|
||||
* repo object directly from the database.
|
||||
*/
|
||||
private Repo loadRepoDetails() {
|
||||
return RepoProvider.Helper.findById(getActivity(), getRepoId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
|
||||
repo = loadRepoDetails();
|
||||
|
||||
if (repo == null) {
|
||||
Log.e(TAG, "Error showing details for repo '" + getRepoId() + "'");
|
||||
return new LinearLayout(container.getContext());
|
||||
}
|
||||
|
||||
ViewGroup repoView = (ViewGroup)inflater.inflate(R.layout.repodetails, null);
|
||||
updateView(repoView);
|
||||
|
||||
// Setup listeners here, rather than in updateView(...),
|
||||
// because otherwise we will end up adding multiple listeners with
|
||||
// subsequent calls to updateView().
|
||||
EditText inputUrl = (EditText)repoView.findViewById(R.id.input_repo_url);
|
||||
inputUrl.addTextChangedListener(new UrlWatcher());
|
||||
|
||||
Button update = (Button)repoView.findViewById(R.id.btn_update);
|
||||
update.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
performUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
return repoView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates relevant views with properties from the current repository.
|
||||
* Decides which views to show and hide depending on the state of the
|
||||
* repository.
|
||||
*/
|
||||
private void updateView(ViewGroup repoView) {
|
||||
|
||||
EditText inputUrl = (EditText)repoView.findViewById(R.id.input_repo_url);
|
||||
inputUrl.setText(repo.address);
|
||||
|
||||
if (repo.hasBeenUpdated()) {
|
||||
updateViewForExistingRepo(repoView);
|
||||
} else {
|
||||
updateViewForNewRepo(repoView);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Help function to make switching between two view states easier.
|
||||
* Perhaps there is a better way to do this. I recall that using Adobe
|
||||
* Flex, there was a thing called "ViewStates" for exactly this. Wonder if
|
||||
* that exists in Android?
|
||||
*/
|
||||
private static void setMultipleViewVisibility(ViewGroup parent,
|
||||
int[] viewIds,
|
||||
int visibility) {
|
||||
for (int viewId : viewIds) {
|
||||
parent.findViewById(viewId).setVisibility(visibility);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateViewForNewRepo(ViewGroup repoView) {
|
||||
setMultipleViewVisibility(repoView, HIDE_IF_EXISTS, View.VISIBLE);
|
||||
setMultipleViewVisibility(repoView, SHOW_IF_EXISTS, View.GONE);
|
||||
}
|
||||
|
||||
private void updateViewForExistingRepo(ViewGroup repoView) {
|
||||
setMultipleViewVisibility(repoView, SHOW_IF_EXISTS, View.VISIBLE);
|
||||
setMultipleViewVisibility(repoView, HIDE_IF_EXISTS, View.GONE);
|
||||
|
||||
TextView name = (TextView)repoView.findViewById(R.id.text_repo_name);
|
||||
TextView numApps = (TextView)repoView.findViewById(R.id.text_num_apps);
|
||||
TextView lastUpdated = (TextView)repoView.findViewById(R.id.text_last_update);
|
||||
|
||||
name.setText(repo.getName());
|
||||
|
||||
int appCount = RepoProvider.Helper.countAppsForRepo(getActivity(), repo.getId());
|
||||
numApps.setText(Integer.toString(appCount));
|
||||
|
||||
setupDescription(repoView, repo);
|
||||
setupRepoFingerprint(repoView, repo);
|
||||
|
||||
// Repos that existed before this feature was supported will have an
|
||||
// "Unknown" last update until next time they update...
|
||||
String lastUpdate = repo.lastUpdated != null
|
||||
? repo.lastUpdated.toString() : getString(R.string.unknown);
|
||||
lastUpdated.setText(lastUpdate);
|
||||
}
|
||||
|
||||
private void setupDescription(ViewGroup parent, Repo repo) {
|
||||
|
||||
TextView descriptionLabel = (TextView)parent.findViewById(R.id.label_description);
|
||||
TextView description = (TextView)parent.findViewById(R.id.text_description);
|
||||
|
||||
if (repo.description == null || repo.description.length() == 0) {
|
||||
descriptionLabel.setVisibility(View.GONE);
|
||||
description.setVisibility(View.GONE);
|
||||
} else {
|
||||
descriptionLabel.setVisibility(View.VISIBLE);
|
||||
description.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
String descriptionText = repo.description == null
|
||||
? "" : repo.description.replaceAll("\n", " ");
|
||||
description.setText(descriptionText);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* When an update is performed, notify the listener so that the repo
|
||||
* list can be updated. We will perform the update ourselves though.
|
||||
*/
|
||||
private void performUpdate() {
|
||||
// Ensure repo is enabled before updating...
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(RepoProvider.DataColumns.IN_USE, 1);
|
||||
RepoProvider.Helper.update(getActivity(), repo, values);
|
||||
|
||||
updateHandler = UpdateService.updateRepoNow(repo.address, getActivity()).setListener(new ProgressListener() {
|
||||
@Override
|
||||
public void onProgress(Event event) {
|
||||
switch (event.type) {
|
||||
case UpdateService.EVENT_COMPLETE_WITH_CHANGES:
|
||||
repo = loadRepoDetails();
|
||||
updateView((ViewGroup)getView());
|
||||
break;
|
||||
case UpdateService.EVENT_FINISHED:
|
||||
updateHandler = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* When the URL is changed, notify the repoChangeListener.
|
||||
*/
|
||||
class UrlWatcher implements TextWatcher {
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {}
|
||||
|
||||
@Override
|
||||
// TODO: This is called each character change, resulting in a DB query.
|
||||
// Doesn't exactly cause performance problems,
|
||||
// but seems silly not to go for more of a "focus out" event then
|
||||
// this "text changed" event.
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
if (!repo.address.equals(s.toString())) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(RepoProvider.DataColumns.ADDRESS, s.toString());
|
||||
RepoProvider.Helper.update(getActivity(), repo, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
menu.clear();
|
||||
|
||||
MenuItem update = menu.add(Menu.NONE, UPDATE, 0, R.string.repo_update);
|
||||
update.setIcon(R.drawable.ic_refresh_white);
|
||||
MenuItemCompat.setShowAsAction(update,
|
||||
MenuItemCompat.SHOW_AS_ACTION_ALWAYS |
|
||||
MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
|
||||
|
||||
MenuItem delete = menu.add(Menu.NONE, DELETE, 0, R.string.delete);
|
||||
delete.setIcon(R.drawable.ic_delete_white);
|
||||
MenuItemCompat.setShowAsAction(delete,
|
||||
MenuItemCompat.SHOW_AS_ACTION_IF_ROOM |
|
||||
MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
if (Build.VERSION.SDK_INT >= 14)
|
||||
prepareNfcMenuItems(menu);
|
||||
}
|
||||
|
||||
@TargetApi(16)
|
||||
private void prepareNfcMenuItems(Menu menu) {
|
||||
boolean needsEnableNfcMenuItem;
|
||||
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
|
||||
if (nfcAdapter == null) {
|
||||
return;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < 16)
|
||||
needsEnableNfcMenuItem = !nfcAdapter.isEnabled();
|
||||
else
|
||||
needsEnableNfcMenuItem = !nfcAdapter.isNdefPushEnabled();
|
||||
if (needsEnableNfcMenuItem) {
|
||||
if (enableNfc != null)
|
||||
return; // already created
|
||||
enableNfc = menu.add(Menu.NONE, ENABLE_NFC, 0, R.string.enable_nfc_send);
|
||||
enableNfc.setIcon(R.drawable.ic_nfc_white);
|
||||
MenuItemCompat.setShowAsAction(enableNfc,
|
||||
MenuItemCompat.SHOW_AS_ACTION_IF_ROOM |
|
||||
MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
|
||||
} else if (enableNfc != null) {
|
||||
// remove the existing MenuItem since NFC is now enabled
|
||||
menu.removeItem(enableNfc.getItemId());
|
||||
enableNfc = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case DELETE:
|
||||
promptForDelete();
|
||||
return true;
|
||||
case UPDATE:
|
||||
performUpdate();
|
||||
return true;
|
||||
case ENABLE_NFC:
|
||||
Intent intent = new Intent(getActivity(), NfcNotEnabledActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void promptForDelete() {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.repo_confirm_delete_title)
|
||||
.setMessage(R.string.repo_confirm_delete_body)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Repo repo = RepoDetailsFragment.this.repo;
|
||||
RepoProvider.Helper.remove(getActivity(), repo.getId());
|
||||
getActivity().finish();
|
||||
}
|
||||
}).setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// Do nothing...
|
||||
}
|
||||
}
|
||||
).show();
|
||||
}
|
||||
|
||||
private void setupRepoFingerprint(ViewGroup parent, Repo repo) {
|
||||
TextView repoFingerprintView = (TextView)parent.findViewById(R.id.text_repo_fingerprint);
|
||||
TextView repoFingerprintDescView = (TextView)parent.findViewById(R.id.text_repo_fingerprint_description);
|
||||
|
||||
String repoFingerprint;
|
||||
int repoFingerprintColor;
|
||||
|
||||
// TODO show the current state of the signature check, not just whether there is a key or not
|
||||
if (TextUtils.isEmpty(repo.fingerprint) && TextUtils.isEmpty(repo.pubkey)) {
|
||||
repoFingerprint = getResources().getString(R.string.unsigned);
|
||||
repoFingerprintColor = getResources().getColor(R.color.unsigned);
|
||||
repoFingerprintDescView.setVisibility(View.VISIBLE);
|
||||
repoFingerprintDescView.setText(getResources().getString(R.string.unsigned_description));
|
||||
} else {
|
||||
// this is based on repo.fingerprint always existing, which it should
|
||||
repoFingerprint = Utils.formatFingerprint(getActivity(), repo.fingerprint);
|
||||
repoFingerprintColor = getResources().getColor(R.color.signed);
|
||||
repoFingerprintDescView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
repoFingerprintView.setText(repoFingerprint);
|
||||
repoFingerprintView.setTextColor(repoFingerprintColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
}
|
@ -1,13 +1,17 @@
|
||||
package org.fdroid.fdroid.views.swap;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.Uri;
|
||||
import android.net.http.AndroidHttpClient;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
@ -20,7 +24,6 @@ import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.ProgressListener;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.UpdateService;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
@ -33,7 +36,7 @@ import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ConnectSwapActivity extends ActionBarActivity implements ProgressListener {
|
||||
public class ConnectSwapActivity extends ActionBarActivity {
|
||||
private static final String TAG = "ConnectSwapActivity";
|
||||
|
||||
private static final String STATE_CONFIRM = "startSwap";
|
||||
@ -79,6 +82,10 @@ public class ConnectSwapActivity extends ActionBarActivity implements ProgressLi
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver,
|
||||
new IntentFilter(UpdateService.LOCAL_ACTION_STATUS));
|
||||
|
||||
// Only confirm the action, and then return a result...
|
||||
newRepoConfig = new NewRepoConfig(this, getIntent());
|
||||
if (newRepoConfig.isValidRepo()) {
|
||||
@ -91,21 +98,28 @@ public class ConnectSwapActivity extends ActionBarActivity implements ProgressLi
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("fallthrough")
|
||||
public void onProgress(Event event) {
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
|
||||
}
|
||||
|
||||
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// TODO: Show progress, but we can worry about that later.
|
||||
// Might be nice to have it nicely embedded in the UI, rather than as
|
||||
// an additional dialog. E.g. White text on blue, letting the user
|
||||
// know what we are up to.
|
||||
int statusCode = intent.getIntExtra(UpdateService.EXTRA_STATUS_CODE, -1);
|
||||
|
||||
switch (event.type) {
|
||||
case UpdateService.EVENT_COMPLETE_AND_SAME:
|
||||
Log.i(TAG, "EVENT_COMPLETE_AND_SAME");
|
||||
case UpdateService.EVENT_COMPLETE_WITH_CHANGES:
|
||||
Log.i(TAG, "EVENT_COMPLETE_WITH_CHANGES");
|
||||
Intent intent = new Intent(this, SwapAppListActivity.class);
|
||||
intent.putExtra(SwapAppListActivity.EXTRA_REPO_ID, repo.getId());
|
||||
startActivity(intent);
|
||||
switch (statusCode) {
|
||||
case UpdateService.STATUS_COMPLETE_AND_SAME:
|
||||
Log.i(TAG, "STATUS_COMPLETE_AND_SAME");
|
||||
case UpdateService.STATUS_COMPLETE_WITH_CHANGES:
|
||||
Log.i(TAG, "STATUS_COMPLETE_WITH_CHANGES");
|
||||
Intent salIntent = new Intent(getBaseContext(), SwapAppListActivity.class);
|
||||
salIntent.putExtra(SwapAppListActivity.EXTRA_REPO_ID, repo.getId());
|
||||
startActivity(salIntent);
|
||||
finish();
|
||||
/*
|
||||
// TODO: Load repo from database to get proper name. This is what the category we want to select will be called.
|
||||
@ -113,18 +127,19 @@ public class ConnectSwapActivity extends ActionBarActivity implements ProgressLi
|
||||
getActivity().setResult(Activity.RESULT_OK, intent);
|
||||
*/
|
||||
break;
|
||||
case UpdateService.EVENT_ERROR:
|
||||
case UpdateService.STATUS_ERROR_GLOBAL:
|
||||
// TODO: Show message on this screen (with a big "okay" button that goes back to F-Droid activity)
|
||||
// rather than finishing directly.
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void confirm() {
|
||||
repo = ensureRepoExists();
|
||||
if (repo != null) {
|
||||
UpdateService.updateRepoNow(repo.address, this).setListener(this);
|
||||
UpdateService.updateRepoNow(repo.address, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user