Merge branch 'appcompat-porting-and-fixes' of https://gitlab.com/eighthave/fdroidclient
This merge request is a lot of porting code to use Android Support appcompat-v7, now that it is in place. There are places where old custom compat layers are replaced by appcompat, and other places like the local repo stuff, where appcompat allows for full support on platforms older than 11.
This commit is contained in:
commit
8fc125a1c5
@ -107,9 +107,13 @@
|
||||
android:name="android.app.default_searchable"
|
||||
android:value=".SearchResults" />
|
||||
</activity>
|
||||
<!--
|
||||
The title for ManageReposActivity is "F-Droid" here, but "Repositories"
|
||||
when viewing the Activity itself in the app.
|
||||
-->
|
||||
<activity
|
||||
android:name=".ManageRepo"
|
||||
android:label="@string/menu_manage"
|
||||
android:name=".views.ManageReposActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:parentActivityName=".FDroid" >
|
||||
<meta-data
|
||||
@ -152,6 +156,8 @@
|
||||
the QR Code for sending the local repo to another device.
|
||||
-->
|
||||
<data android:path="/FDROID/REPO" />
|
||||
<data android:path="/.*/FDROID/REPO" />
|
||||
<data android:path="/.*/.*/FDROID/REPO" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
@ -218,7 +224,7 @@
|
||||
<activity
|
||||
android:name=".views.RepoDetailsActivity"
|
||||
android:label="@string/menu_manage"
|
||||
android:parentActivityName=".ManageRepo"
|
||||
android:parentActivityName=".views.ManageReposActivity"
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
|
55
res/layout-v11/select_local_apps_list_item.xml
Normal file
55
res/layout-v11/select_local_apps_list_item.xml
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2010 The Android Open Source Project
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/activatedBackgroundIndicator"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:paddingBottom="2dip"
|
||||
android:paddingTop="2dip" >
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dip"
|
||||
android:layout_height="48dip"
|
||||
android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_marginTop="6dip"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TwoLineListItem
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:mode="twoLine" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/application_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_marginTop="6dip"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/package_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignStart="@+id/application_label"
|
||||
android:layout_below="@+id/application_label"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
</TwoLineListItem>
|
||||
|
||||
</LinearLayout>
|
@ -1,28 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:baselineAligned="false" >
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:paddingBottom="2dip"
|
||||
android:paddingTop="2dip" >
|
||||
|
||||
<!-- Actual icon size is dependent on preferences and set in
|
||||
AppListAdapater.java:layoutIcon() -->
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:contentDescription="@string/app_icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_width="48dip"
|
||||
android:layout_height="48dip"
|
||||
android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_marginTop="6dip"
|
||||
android:scaleType="fitCenter"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingStart="10dp"
|
||||
android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:layout_marginRight="?attr/listPreferredItemPaddingRight"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:baselineAligned="false" >
|
||||
|
||||
@ -54,8 +58,8 @@
|
||||
android:layout_weight="1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="end"
|
||||
android:textAlignment="viewEnd"
|
||||
@ -89,8 +93,8 @@
|
||||
android:layout_weight="1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="6sp"
|
||||
android:layout_marginStart="6sp"
|
||||
android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="end"
|
||||
android:textAlignment="viewEnd"
|
||||
|
@ -16,19 +16,15 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/activatedBackgroundIndicator"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:minHeight="?attr/listPreferredItemHeight"
|
||||
android:paddingBottom="2dip"
|
||||
android:paddingTop="2dip"
|
||||
tools:ignore="NewApi" >
|
||||
|
||||
<!-- TODO remove NewApi ignore when appcompat-v7 is in place -->
|
||||
android:paddingTop="2dip" >
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dip"
|
||||
android:layout_height="48dip"
|
||||
android:layout_marginLeft="?android:attr/listPreferredItemPaddingLeft"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingLeft"
|
||||
android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_marginTop="6dip"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
@ -41,16 +37,17 @@
|
||||
android:id="@+id/application_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="?android:attr/listPreferredItemPaddingLeft"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingLeft"
|
||||
android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_marginTop="6dip"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem" />
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/package_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignStart="@+id/application_label"
|
||||
android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_below="@+id/application_label"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
</TwoLineListItem>
|
||||
|
@ -1,20 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto" >
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_setup_repo"
|
||||
android:icon="@android:drawable/ic_input_add"
|
||||
android:showAsAction="ifRoom|withText"
|
||||
android:title="@string/setup_repo"/>
|
||||
android:title="@string/setup_repo"
|
||||
app:showAsAction="ifRoom|withText"/>
|
||||
<item
|
||||
android:id="@+id/menu_send_fdroid_via_wifi"
|
||||
android:icon="@android:drawable/arrow_up_float"
|
||||
android:showAsAction="never"
|
||||
android:title="@string/send_fdroid_via_wifi"/>
|
||||
android:title="@string/send_fdroid_via_wifi"
|
||||
app:showAsAction="never"/>
|
||||
<item
|
||||
android:id="@+id/menu_settings"
|
||||
android:icon="@android:drawable/ic_menu_preferences"
|
||||
android:showAsAction="never"
|
||||
android:title="@string/menu_preferences"/>
|
||||
android:title="@string/menu_preferences"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
</menu>
|
||||
</menu>
|
40
res/menu/main.xml
Normal file
40
res/menu/main.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?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/action_search"
|
||||
android:icon="@android:drawable/ic_menu_search"
|
||||
android:title="@string/menu_search"
|
||||
app:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/action_update_repo"
|
||||
android:icon="@drawable/ic_menu_refresh"
|
||||
android:title="@string/menu_update_repo"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/action_local_repo"
|
||||
android:title="@string/local_repo"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/action_manage_repos"
|
||||
android:icon="@android:drawable/ic_menu_agenda"
|
||||
android:title="@string/menu_manage"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/action_bluetooth_apk"
|
||||
android:icon="@android:drawable/stat_sys_data_bluetooth"
|
||||
android:title="@string/menu_send_apk_bt"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:icon="@android:drawable/ic_menu_preferences"
|
||||
android:title="@string/menu_preferences"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/action_about"
|
||||
android:icon="@android:drawable/ic_menu_help"
|
||||
android:title="@string/menu_about"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
</menu>
|
20
res/menu/manage_repos.xml
Normal file
20
res/menu/manage_repos.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?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/action_update_repo"
|
||||
android:icon="@drawable/ic_menu_refresh"
|
||||
android:title="@string/menu_update_repo"
|
||||
app:showAsAction="always|withText"/>
|
||||
<item
|
||||
android:id="@+id/action_add_repo"
|
||||
android:icon="@android:drawable/ic_menu_add"
|
||||
android:title="@string/menu_add_repo"
|
||||
app:showAsAction="always|withText"/>
|
||||
<item
|
||||
android:id="@+id/action_find_local_repos"
|
||||
android:title="@string/menu_scan_repo"
|
||||
app:showAsAction="ifRoom|withText"/>
|
||||
|
||||
</menu>
|
@ -1,14 +1,17 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<?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/action_search"
|
||||
android:icon="@android:drawable/ic_menu_search"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="@string/menu_search"/>
|
||||
android:title="@string/menu_search"
|
||||
app:actionViewClass="android.support.v7.widget.SearchView"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/action_update_repo"
|
||||
android:icon="@android:drawable/ic_input_add"
|
||||
android:showAsAction="always|withText"
|
||||
android:title="@string/update_repo"/>
|
||||
android:title="@string/update_repo"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
</menu>
|
@ -1,16 +1,17 @@
|
||||
<?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" >
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto" >
|
||||
|
||||
<item
|
||||
android:id="@+id/action_search"
|
||||
app:actionViewClass="android.support.v7.widget.SearchView"
|
||||
app:showAsAction="collapseActionView|always"
|
||||
android:icon="@android:drawable/ic_menu_search"
|
||||
android:title="@string/menu_search"/>
|
||||
android:title="@string/menu_search"
|
||||
app:actionViewClass="android.support.v7.widget.SearchView"
|
||||
app:showAsAction="collapseActionView|always"/>
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:icon="@android:drawable/ic_menu_preferences"
|
||||
android:title="@string/menu_preferences"
|
||||
app:showAsAction="never"/>
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
</menu>
|
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="applist_icon_normal_size">48dp</dimen>
|
||||
<dimen name="applist_icon_compact_size">32dp</dimen>
|
||||
</resources>
|
@ -1,6 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<dimen name="padding_side">8dp</dimen>
|
||||
<dimen name="padding_top">5dp</dimen>
|
||||
<dimen name="form_label_top">5dp</dimen>
|
||||
<dimen name="applist_icon_normal_size">48dp</dimen>
|
||||
<dimen name="applist_icon_compact_size">32dp</dimen>
|
||||
|
||||
</resources>
|
@ -59,12 +59,12 @@ import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
|
||||
|
||||
import org.fdroid.fdroid.Utils.CommaSeparatedList;
|
||||
import org.fdroid.fdroid.compat.ActionBarCompat;
|
||||
import org.fdroid.fdroid.compat.MenuManager;
|
||||
import org.fdroid.fdroid.compat.PackageManagerCompat;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.ApkProvider;
|
||||
@ -126,18 +126,18 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
TextView added;
|
||||
TextView nativecode;
|
||||
}
|
||||
|
||||
|
||||
// observer to update view when package has been installed/deleted
|
||||
AppObserver myAppObserver;
|
||||
class AppObserver extends ContentObserver {
|
||||
public AppObserver(Handler handler) {
|
||||
super(handler);
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
this.onChange(selfChange, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
@ -147,7 +147,7 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
}
|
||||
|
||||
refreshApkList();
|
||||
MenuManager.create(AppDetails.this).invalidateOptionsMenu();
|
||||
supportInvalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@ -384,7 +384,7 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
mPm = getPackageManager();
|
||||
|
||||
installer = Installer.getActivityInstaller(this, mPm, myInstallerCallback);
|
||||
|
||||
|
||||
// Get the preferences we're going to use in this Activity...
|
||||
ConfigurationChangeHelper previousData = (ConfigurationChangeHelper)getLastCustomNonConfigurationInstance();
|
||||
if (previousData != null) {
|
||||
@ -409,10 +409,7 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
// fragments, which rely on data from the activity that is set earlier in this method.
|
||||
setContentView(R.layout.app_details);
|
||||
|
||||
// Actionbar cannot be accessed until after setContentView (on 3.0 and 3.1 devices)
|
||||
// see: http://blog.perpetumdesign.com/2011/08/strange-case-of-dr-action-and-mr-bar.html
|
||||
// for reason why.
|
||||
ActionBarCompat.create(this).setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
// Check for the presence of a view which only exists in the landscape view.
|
||||
// This seems to be the preferred way to interrogate the view, rather than
|
||||
@ -463,7 +460,7 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
protected void onResumeFragments() {
|
||||
super.onResumeFragments();
|
||||
refreshApkList();
|
||||
MenuManager.create(this).invalidateOptionsMenu();
|
||||
supportInvalidateOptionsMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -800,6 +797,7 @@ 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) {
|
||||
String [] projection = { RepoProvider.DataColumns.ADDRESS };
|
||||
Repo repo = RepoProvider.Helper.findById(this, apk.repo, projection);
|
||||
@ -883,7 +881,7 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
public void onSuccess(final int operation) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
public void run() {
|
||||
if (operation == Installer.InstallerCallback.OPERATION_INSTALL) {
|
||||
PackageManagerCompat.setInstaller(mPm, app.id);
|
||||
}
|
||||
@ -907,9 +905,9 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
@Override
|
||||
public void run() {
|
||||
setProgressBarIndeterminateVisibility(false);
|
||||
|
||||
|
||||
Log.e(TAG, "Installer aborted with errorCode: " + errorCode);
|
||||
|
||||
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(AppDetails.this);
|
||||
alertBuilder.setTitle(R.string.installer_error_title);
|
||||
alertBuilder.setMessage(R.string.installer_error_title);
|
||||
@ -1049,7 +1047,7 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
if (installer.handleOnActivityResult(requestCode, resultCode, data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_ENABLE_BLUETOOTH:
|
||||
fdroidApp.sendViaBluetooth(this, resultCode, app.id);
|
||||
@ -1057,18 +1055,22 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public App getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApkListAdapter getApks() {
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Signature getInstalledSignature() {
|
||||
return mInstalledSignature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInstalledSignatureId() {
|
||||
return mInstalledSigID;
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.util.Log;
|
||||
@ -42,10 +41,12 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.fdroid.fdroid.compat.TabManager;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.views.AppListFragmentPageAdapter;
|
||||
import org.fdroid.fdroid.views.AppListFragmentPagerAdapter;
|
||||
import org.fdroid.fdroid.views.LocalRepoActivity;
|
||||
import org.fdroid.fdroid.views.ManageReposActivity;
|
||||
|
||||
public class FDroid extends ActionBarActivity {
|
||||
|
||||
@ -56,14 +57,6 @@ public class FDroid extends ActionBarActivity {
|
||||
|
||||
public static final String EXTRA_TAB_UPDATE = "extraTab";
|
||||
|
||||
private static final int UPDATE_REPO = Menu.FIRST;
|
||||
private static final int MANAGE_REPO = Menu.FIRST + 1;
|
||||
private static final int PREFERENCES = Menu.FIRST + 2;
|
||||
private static final int ABOUT = Menu.FIRST + 3;
|
||||
private static final int SEARCH = Menu.FIRST + 4;
|
||||
private static final int BLUETOOTH_APK = Menu.FIRST + 5;
|
||||
private static final int LOCAL_REPO = Menu.FIRST + 6;
|
||||
|
||||
private FDroidApp fdroidApp = null;
|
||||
|
||||
private ViewPager viewPager;
|
||||
@ -124,23 +117,13 @@ public class FDroid extends ActionBarActivity {
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
|
||||
super.onCreateOptionsMenu(menu);
|
||||
menu.add(Menu.NONE, UPDATE_REPO, 1, R.string.menu_update_repo).setIcon(
|
||||
android.R.drawable.ic_menu_rotate);
|
||||
menu.add(Menu.NONE, MANAGE_REPO, 2, R.string.menu_manage).setIcon(
|
||||
android.R.drawable.ic_menu_agenda);
|
||||
MenuItem search = menu.add(Menu.NONE, SEARCH, 3, R.string.menu_search).setIcon(
|
||||
android.R.drawable.ic_menu_search);
|
||||
if (fdroidApp.bluetoothAdapter != null) // ignore on devices without Bluetooth
|
||||
menu.add(Menu.NONE, BLUETOOTH_APK, 3, R.string.menu_send_apk_bt);
|
||||
menu.add(Menu.NONE, LOCAL_REPO, 4, R.string.local_repo);
|
||||
menu.add(Menu.NONE, PREFERENCES, 4, R.string.menu_preferences).setIcon(
|
||||
android.R.drawable.ic_menu_preferences);
|
||||
menu.add(Menu.NONE, ABOUT, 5, R.string.menu_about).setIcon(
|
||||
android.R.drawable.ic_menu_help);
|
||||
MenuItemCompat.setShowAsAction(search, MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
|
||||
return true;
|
||||
getMenuInflater().inflate(R.menu.main, menu);
|
||||
if (fdroidApp.bluetoothAdapter == null) {
|
||||
// ignore on devices without Bluetooth
|
||||
MenuItem btItem = menu.findItem(R.id.action_bluetooth_apk);
|
||||
btItem.setVisible(false);
|
||||
}
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -148,91 +131,91 @@ public class FDroid extends ActionBarActivity {
|
||||
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case UPDATE_REPO:
|
||||
updateRepos();
|
||||
return true;
|
||||
case R.id.action_update_repo:
|
||||
updateRepos();
|
||||
return true;
|
||||
|
||||
case MANAGE_REPO:
|
||||
Intent i = new Intent(this, ManageRepo.class);
|
||||
startActivityForResult(i, REQUEST_MANAGEREPOS);
|
||||
return true;
|
||||
case R.id.action_manage_repos:
|
||||
Intent i = new Intent(this, ManageReposActivity.class);
|
||||
startActivityForResult(i, REQUEST_MANAGEREPOS);
|
||||
return true;
|
||||
|
||||
case PREFERENCES:
|
||||
Intent prefs = new Intent(getBaseContext(), PreferencesActivity.class);
|
||||
startActivityForResult(prefs, REQUEST_PREFS);
|
||||
return true;
|
||||
case R.id.action_settings:
|
||||
Intent prefs = new Intent(getBaseContext(), PreferencesActivity.class);
|
||||
startActivityForResult(prefs, REQUEST_PREFS);
|
||||
return true;
|
||||
|
||||
case LOCAL_REPO:
|
||||
startActivity(new Intent(this, LocalRepoActivity.class));
|
||||
return true;
|
||||
case R.id.action_local_repo:
|
||||
startActivity(new Intent(this, LocalRepoActivity.class));
|
||||
return true;
|
||||
|
||||
case SEARCH:
|
||||
onSearchRequested();
|
||||
return true;
|
||||
case R.id.action_search:
|
||||
onSearchRequested();
|
||||
return true;
|
||||
|
||||
case BLUETOOTH_APK:
|
||||
/*
|
||||
* If Bluetooth has not been enabled/turned on, then
|
||||
* enabling device discoverability will automatically enable Bluetooth
|
||||
*/
|
||||
Intent discoverBt = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
|
||||
discoverBt.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 121);
|
||||
startActivityForResult(discoverBt, REQUEST_ENABLE_BLUETOOTH);
|
||||
// if this is successful, the Bluetooth transfer is started
|
||||
return true;
|
||||
case R.id.action_bluetooth_apk:
|
||||
/*
|
||||
* If Bluetooth has not been enabled/turned on, then enabling
|
||||
* device discoverability will automatically enable Bluetooth
|
||||
*/
|
||||
Intent discoverBt = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
|
||||
discoverBt.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 121);
|
||||
startActivityForResult(discoverBt, REQUEST_ENABLE_BLUETOOTH);
|
||||
// if this is successful, the Bluetooth transfer is started
|
||||
return true;
|
||||
|
||||
case ABOUT:
|
||||
View view = null;
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
LayoutInflater li = LayoutInflater.from(this);
|
||||
view = li.inflate(R.layout.about, null);
|
||||
} else {
|
||||
view = View.inflate(
|
||||
new ContextThemeWrapper(this, R.style.AboutDialogLight),
|
||||
R.layout.about, null);
|
||||
}
|
||||
case R.id.action_about:
|
||||
View view = null;
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
LayoutInflater li = LayoutInflater.from(this);
|
||||
view = li.inflate(R.layout.about, null);
|
||||
} else {
|
||||
view = View.inflate(
|
||||
new ContextThemeWrapper(this, R.style.AboutDialogLight),
|
||||
R.layout.about, null);
|
||||
}
|
||||
|
||||
// Fill in the version...
|
||||
try {
|
||||
PackageInfo pi = getPackageManager()
|
||||
.getPackageInfo(getApplicationContext()
|
||||
.getPackageName(), 0);
|
||||
((TextView) view.findViewById(R.id.version))
|
||||
.setText(pi.versionName);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
// Fill in the version...
|
||||
try {
|
||||
PackageInfo pi = getPackageManager()
|
||||
.getPackageInfo(getApplicationContext()
|
||||
.getPackageName(), 0);
|
||||
((TextView) view.findViewById(R.id.version))
|
||||
.setText(pi.versionName);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
Builder p = null;
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
p = new AlertDialog.Builder(this).setView(view);
|
||||
} else {
|
||||
p = new AlertDialog.Builder(
|
||||
new ContextThemeWrapper(
|
||||
this, R.style.AboutDialogLight)
|
||||
).setView(view);
|
||||
}
|
||||
final AlertDialog alrt = p.create();
|
||||
alrt.setIcon(R.drawable.ic_launcher);
|
||||
alrt.setTitle(getString(R.string.about_title));
|
||||
alrt.setButton(AlertDialog.BUTTON_NEUTRAL,
|
||||
getString(R.string.about_website),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int whichButton) {
|
||||
Uri uri = Uri.parse("https://f-droid.org");
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, uri));
|
||||
}
|
||||
});
|
||||
alrt.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.ok),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int whichButton) {
|
||||
}
|
||||
});
|
||||
alrt.show();
|
||||
return true;
|
||||
Builder p = null;
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
p = new AlertDialog.Builder(this).setView(view);
|
||||
} else {
|
||||
p = new AlertDialog.Builder(
|
||||
new ContextThemeWrapper(
|
||||
this, R.style.AboutDialogLight)
|
||||
).setView(view);
|
||||
}
|
||||
final AlertDialog alrt = p.create();
|
||||
alrt.setIcon(R.drawable.ic_launcher);
|
||||
alrt.setTitle(getString(R.string.about_title));
|
||||
alrt.setButton(AlertDialog.BUTTON_NEUTRAL,
|
||||
getString(R.string.about_website),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int whichButton) {
|
||||
Uri uri = Uri.parse("https://f-droid.org");
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, uri));
|
||||
}
|
||||
});
|
||||
alrt.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.ok),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int whichButton) {
|
||||
}
|
||||
});
|
||||
alrt.show();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@ -244,7 +227,7 @@ public class FDroid extends ActionBarActivity {
|
||||
case REQUEST_APPDETAILS:
|
||||
break;
|
||||
case REQUEST_MANAGEREPOS:
|
||||
if (data != null && data.hasExtra(ManageRepo.REQUEST_UPDATE)) {
|
||||
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.setIcon(android.R.drawable.ic_menu_rotate);
|
||||
@ -293,8 +276,8 @@ public class FDroid extends ActionBarActivity {
|
||||
|
||||
private void createViews() {
|
||||
viewPager = (ViewPager)findViewById(R.id.main_pager);
|
||||
AppListFragmentPageAdapter viewPageAdapter = new AppListFragmentPageAdapter(this);
|
||||
viewPager.setAdapter(viewPageAdapter);
|
||||
AppListFragmentPagerAdapter viewPagerAdapter = new AppListFragmentPagerAdapter(this);
|
||||
viewPager.setAdapter(viewPagerAdapter);
|
||||
viewPager.setOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
|
@ -1,190 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com
|
||||
* Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
import org.fdroid.fdroid.views.fragments.RepoListFragment;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class ManageRepo 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 RepoListFragment listFragment;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
((FDroidApp) getApplication()).applyTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
if (fm.findFragmentById(android.R.id.content) == 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
|
||||
setContentView( new LinearLayout(this) );
|
||||
|
||||
listFragment = new RepoListFragment();
|
||||
fm.beginTransaction()
|
||||
.add(android.R.id.content, listFragment)
|
||||
.commit();
|
||||
}
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
/* let's see if someone is trying to send us a new repo */
|
||||
addRepoFromIntent(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
addRepoFromIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
Intent ret = new Intent();
|
||||
markChangedIfRequired(ret);
|
||||
setResult(Activity.RESULT_OK, ret);
|
||||
super.finish();
|
||||
}
|
||||
|
||||
private boolean hasChanged() {
|
||||
return listFragment != null && listFragment.hasChanged();
|
||||
}
|
||||
|
||||
private void markChangedIfRequired(Intent intent) {
|
||||
if (hasChanged()) {
|
||||
Log.i("FDroid", "Repo details have changed, prompting for update.");
|
||||
intent.putExtra(REQUEST_UPDATE, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
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;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void addRepoFromIntent(Intent intent) {
|
||||
/* an URL from a click, NFC, QRCode scan, etc */
|
||||
Uri uri = intent.getData();
|
||||
if (uri != null) {
|
||||
// scheme and host should only ever be pure ASCII aka Locale.ENGLISH
|
||||
String scheme = intent.getScheme();
|
||||
String host = uri.getHost();
|
||||
if (scheme == null || host == null) {
|
||||
String msg = String.format(getString(R.string.malformed_repo_uri), uri);
|
||||
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
if (scheme.equals("FDROIDREPO") || scheme.equals("FDROIDREPOS")) {
|
||||
/*
|
||||
* QRCodes are more efficient in all upper case, so QR URIs are
|
||||
* encoded in all upper case, then forced to lower case.
|
||||
* Checking if the special F-Droid scheme being all is upper
|
||||
* case means it should be downcased.
|
||||
*/
|
||||
uri = Uri.parse(uri.toString().toLowerCase(Locale.ENGLISH));
|
||||
} else if (uri.getPath().startsWith("/FDROID/REPO")) {
|
||||
/*
|
||||
* some QR scanners chop off the fdroidrepo:// and just try
|
||||
* http://, then the incoming URI does not get downcased
|
||||
* properly, and the query string is stripped off. So just
|
||||
* downcase the path, and carry on to get something working.
|
||||
*/
|
||||
uri = Uri.parse(uri.toString().toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
// make scheme and host lowercase so they're readable in dialogs
|
||||
scheme = scheme.toLowerCase(Locale.ENGLISH);
|
||||
host = host.toLowerCase(Locale.ENGLISH);
|
||||
String fingerprint = uri.getQueryParameter("fingerprint");
|
||||
Log.i("RepoListFragment", "onCreate " + fingerprint);
|
||||
if (scheme.equals("fdroidrepos") || scheme.equals("fdroidrepo")
|
||||
|| scheme.equals("https") || scheme.equals("http")) {
|
||||
|
||||
/* sanitize and format for function and readability */
|
||||
String uriString = uri.toString()
|
||||
.replaceAll("\\?.*$", "") // remove the whole query
|
||||
.replaceAll("/*$", "") // remove all trailing slashes
|
||||
.replace(uri.getHost(), host) // downcase host name
|
||||
.replace(intent.getScheme(), scheme) // downcase scheme
|
||||
.replace("fdroidrepo", "http"); // proper repo address
|
||||
listFragment.importRepo(uriString, fingerprint);
|
||||
|
||||
// if this is a local repo, check we're on the same wifi
|
||||
String uriBssid = uri.getQueryParameter("bssid");
|
||||
if (!TextUtils.isEmpty(uriBssid)) {
|
||||
if (uri.getPort() != 8888
|
||||
&& !host.matches("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+")) {
|
||||
Log.i("ManageRepo", "URI is not local repo: " + uri);
|
||||
return;
|
||||
}
|
||||
WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
|
||||
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
|
||||
String bssid = wifiInfo.getBSSID().toLowerCase(Locale.ENGLISH);
|
||||
uriBssid = Uri.decode(uriBssid).toLowerCase(Locale.ENGLISH);
|
||||
if (!bssid.equals(uriBssid)) {
|
||||
String msg = String.format(getString(R.string.not_on_same_wifi),
|
||||
uri.getQueryParameter("ssid"));
|
||||
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
// TODO we should help the user to the right thing here,
|
||||
// instead of just showing a message!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,25 +18,49 @@
|
||||
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.IntentService;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.OperationApplicationException;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.database.Cursor;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.*;
|
||||
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.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
import org.fdroid.fdroid.data.*;
|
||||
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.ApkProvider;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.net.Downloader;
|
||||
import org.fdroid.fdroid.updater.RepoUpdater;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class UpdateService extends IntentService implements ProgressListener {
|
||||
|
||||
|
@ -1,64 +0,0 @@
|
||||
package org.fdroid.fdroid.compat;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActionBar;
|
||||
import android.app.Activity;
|
||||
|
||||
public abstract class ActionBarCompat extends Compatibility {
|
||||
|
||||
public static ActionBarCompat create(Activity activity) {
|
||||
if (hasApi(11)) {
|
||||
return new HoneycombActionBarCompatImpl(activity);
|
||||
} else {
|
||||
return new OldActionBarCompatImpl(activity);
|
||||
}
|
||||
}
|
||||
|
||||
protected final Activity activity;
|
||||
|
||||
public ActionBarCompat(Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cannot be accessed until after setContentView (on 3.0 and 3.1 devices) has
|
||||
* been called on the relevant activity. If you don't have a content view
|
||||
* (e.g. when using fragment manager to add fragments to the activity) then you
|
||||
* will still need to call setContentView(), with a "new LinearLayout()" or something
|
||||
* useless like that.
|
||||
* See: http://blog.perpetumdesign.com/2011/08/strange-case-of-dr-action-and-mr-bar.html
|
||||
* for details.
|
||||
*/
|
||||
public abstract void setDisplayHomeAsUpEnabled(boolean value);
|
||||
|
||||
}
|
||||
|
||||
class OldActionBarCompatImpl extends ActionBarCompat {
|
||||
|
||||
public OldActionBarCompatImpl(Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayHomeAsUpEnabled(boolean value) {
|
||||
// Do nothing...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TargetApi(11)
|
||||
class HoneycombActionBarCompatImpl extends ActionBarCompat {
|
||||
|
||||
private final ActionBar actionBar;
|
||||
|
||||
public HoneycombActionBarCompatImpl(Activity activity) {
|
||||
super(activity);
|
||||
this.actionBar = activity.getActionBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayHomeAsUpEnabled(boolean value) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(value);
|
||||
}
|
||||
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package org.fdroid.fdroid.compat;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
|
||||
abstract public class MenuManager extends Compatibility {
|
||||
|
||||
public static MenuManager create(Activity activity) {
|
||||
if (hasApi(11)) {
|
||||
return new HoneycombMenuManagerImpl(activity);
|
||||
} else {
|
||||
return new OldMenuManagerImpl(activity);
|
||||
}
|
||||
}
|
||||
|
||||
protected final Activity activity;
|
||||
|
||||
protected MenuManager(Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
abstract public void invalidateOptionsMenu();
|
||||
|
||||
}
|
||||
|
||||
class OldMenuManagerImpl extends MenuManager {
|
||||
|
||||
protected OldMenuManagerImpl(Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateOptionsMenu() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TargetApi(11)
|
||||
class HoneycombMenuManagerImpl extends MenuManager {
|
||||
|
||||
protected HoneycombMenuManagerImpl(Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateOptionsMenu() {
|
||||
activity.invalidateOptionsMenu();
|
||||
}
|
||||
}
|
137
src/org/fdroid/fdroid/data/NewRepoConfig.java
Normal file
137
src/org/fdroid/fdroid/data/NewRepoConfig.java
Normal file
@ -0,0 +1,137 @@
|
||||
|
||||
package org.fdroid.fdroid.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
public class NewRepoConfig {
|
||||
|
||||
private String errorMessage;
|
||||
private boolean isValidRepo;
|
||||
|
||||
private String uriString;
|
||||
private Uri uri;
|
||||
private String host;
|
||||
private int port = -1;
|
||||
private String scheme;
|
||||
private String fingerprint;
|
||||
private String bssid;
|
||||
private String ssid;
|
||||
|
||||
public NewRepoConfig(Context context, Intent intent) {
|
||||
/* an URL from a click, NFC, QRCode scan, etc */
|
||||
uri = intent.getData();
|
||||
if (uri == null) {
|
||||
isValidRepo = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// scheme and host should only ever be pure ASCII aka Locale.ENGLISH
|
||||
scheme = intent.getScheme();
|
||||
host = uri.getHost();
|
||||
port = uri.getPort();
|
||||
if (TextUtils.isEmpty(scheme) || TextUtils.isEmpty(host)) {
|
||||
errorMessage = String.format(context.getString(R.string.malformed_repo_uri),
|
||||
uri);
|
||||
isValidRepo = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Arrays.asList("FDROIDREPO", "FDROIDREPOS").contains(scheme)) {
|
||||
/*
|
||||
* QRCodes are more efficient in all upper case, so QR URIs are
|
||||
* encoded in all upper case, then forced to lower case. Checking if
|
||||
* the special F-Droid scheme being all is upper case means it
|
||||
* should be downcased.
|
||||
*/
|
||||
uri = Uri.parse(uri.toString().toLowerCase(Locale.ENGLISH));
|
||||
} else if (uri.getPath().endsWith("/FDROID/REPO")) {
|
||||
/*
|
||||
* some QR scanners chop off the fdroidrepo:// and just try http://,
|
||||
* then the incoming URI does not get downcased properly, and the
|
||||
* query string is stripped off. So just downcase the path, and
|
||||
* carry on to get something working.
|
||||
*/
|
||||
uri = Uri.parse(uri.toString().toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
// make scheme and host lowercase so they're readable in dialogs
|
||||
scheme = scheme.toLowerCase(Locale.ENGLISH);
|
||||
host = host.toLowerCase(Locale.ENGLISH);
|
||||
fingerprint = uri.getQueryParameter("fingerprint");
|
||||
bssid = uri.getQueryParameter("bssid");
|
||||
ssid = uri.getQueryParameter("ssid");
|
||||
|
||||
Log.i("RepoListFragment", "onCreate " + fingerprint);
|
||||
if (Arrays.asList("fdroidrepos", "fdroidrepo", "https", "http").contains(scheme)) {
|
||||
uriString = sanitizeRepoUri(uri);
|
||||
}
|
||||
|
||||
this.isValidRepo = true;
|
||||
}
|
||||
|
||||
public String getBssid() {
|
||||
return bssid;
|
||||
}
|
||||
|
||||
public String getSsid() {
|
||||
return ssid;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public String getUriString() {
|
||||
return uriString;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public String getScheme() {
|
||||
return scheme;
|
||||
}
|
||||
|
||||
public String getFingerprint() {
|
||||
return fingerprint;
|
||||
}
|
||||
|
||||
public boolean isValidRepo() {
|
||||
return isValidRepo;
|
||||
}
|
||||
|
||||
/*
|
||||
* The port starts out as 8888, but if there is a conflict, it will be
|
||||
* incremented until there is a free port found.
|
||||
*/
|
||||
public boolean looksLikeLocalRepo() {
|
||||
return (port >= 8888 && host.matches("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+"));
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
/** Sanitize and format an incoming repo URI for function and readability */
|
||||
public static String sanitizeRepoUri(Uri uri) {
|
||||
String scheme = uri.getScheme();
|
||||
String host = uri.getHost();
|
||||
return uri.toString()
|
||||
.replaceAll("\\?.*$", "") // remove the whole query
|
||||
.replaceAll("/*$", "") // remove all trailing slashes
|
||||
.replace(host, host.toLowerCase(Locale.ENGLISH))
|
||||
.replace(scheme, scheme.toLowerCase(Locale.ENGLISH))
|
||||
.replace("fdroidrepo", "http") // proper repo address
|
||||
.replace("/FDROID/REPO", "/fdroid/repo"); // for QR FDroid path
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package org.fdroid.fdroid.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
@ -11,10 +10,12 @@ import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
|
||||
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
|
||||
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
|
@ -2,6 +2,7 @@ package org.fdroid.fdroid.views;
|
||||
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
|
||||
import org.fdroid.fdroid.FDroid;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
@ -13,18 +14,18 @@ import org.fdroid.fdroid.views.fragments.InstalledAppsFragment;
|
||||
* Used by the FDroid activity in conjunction with its ViewPager to support
|
||||
* swiping of tabs for both old devices (< 3.0) and new devices.
|
||||
*/
|
||||
public class AppListFragmentPageAdapter extends FragmentPagerAdapter {
|
||||
public class AppListFragmentPagerAdapter extends FragmentPagerAdapter {
|
||||
|
||||
private FDroid parent = null;
|
||||
|
||||
public AppListFragmentPageAdapter(FDroid parent) {
|
||||
public AppListFragmentPagerAdapter(FDroid parent) {
|
||||
super(parent.getSupportFragmentManager());
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
private String getUpdateTabTitle() {
|
||||
int updateCount = AppProvider.Helper.count(parent, AppProvider.getCanUpdateUri());
|
||||
|
||||
|
||||
// TODO: Make RTL friendly, probably by having a different string for both tab_updates_none and tab_updates
|
||||
return parent.getString(R.string.tab_updates) + " (" + updateCount + ")";
|
||||
}
|
@ -30,6 +30,7 @@ import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.PreferencesActivity;
|
||||
import org.fdroid.fdroid.QrGenAsyncTask;
|
||||
@ -158,8 +159,6 @@ public class LocalRepoActivity extends ActionBarActivity {
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.local_repo_activity, menu);
|
||||
if (Build.VERSION.SDK_INT < 14) // TODO remove after including appcompat-v7
|
||||
menu.findItem(R.id.menu_setup_repo).setVisible(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
616
src/org/fdroid/fdroid/views/ManageReposActivity.java
Normal file
616
src/org/fdroid/fdroid/views/ManageReposActivity.java
Normal file
@ -0,0 +1,616 @@
|
||||
/*
|
||||
* Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com
|
||||
* Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.fdroid.fdroid.views;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
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;
|
||||
import org.fdroid.fdroid.data.NewRepoConfig;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
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.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
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 RepoListFragment listFragment;
|
||||
private AlertDialog addRepoDialog;
|
||||
private static final String DEFAULT_NEW_REPO_TEXT = "https://";
|
||||
|
||||
private enum PositiveAction {
|
||||
ADD_NEW, ENABLE, IGNORE
|
||||
}
|
||||
|
||||
private PositiveAction positiveAction;
|
||||
|
||||
private UpdateService.UpdateReceiver updateHandler = null;
|
||||
|
||||
private static boolean changed = false;
|
||||
|
||||
/**
|
||||
* True if activity started with an intent such as from QR code. False if
|
||||
* opened from, e.g. the main menu.
|
||||
*/
|
||||
private boolean isImportingRepo = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
((FDroidApp) getApplication()).applyTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
if (fm.findFragmentById(android.R.id.content) == 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
|
||||
*/
|
||||
setContentView(new LinearLayout(this));
|
||||
|
||||
listFragment = new RepoListFragment();
|
||||
fm.beginTransaction()
|
||||
.add(android.R.id.content, listFragment)
|
||||
.commit();
|
||||
}
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
// title is "Repositories" here, but "F-Droid" in VIEW Intent chooser
|
||||
getSupportActionBar().setTitle(R.string.menu_manage);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
Intent ret = new Intent();
|
||||
markChangedIfRequired(ret);
|
||||
setResult(Activity.RESULT_OK, ret);
|
||||
super.finish();
|
||||
}
|
||||
|
||||
private boolean hasChanged() {
|
||||
return changed;
|
||||
}
|
||||
|
||||
private void markChangedIfRequired(Intent intent) {
|
||||
if (hasChanged()) {
|
||||
Log.i("FDroid", "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);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
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;
|
||||
case R.id.action_add_repo:
|
||||
showAddRepo();
|
||||
return true;
|
||||
case R.id.action_update_repo:
|
||||
updateRepos();
|
||||
return true;
|
||||
case R.id.action_find_local_repos:
|
||||
scanForRepos();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void updateRepos() {
|
||||
updateHandler = UpdateService.updateNow(this).setListener(
|
||||
new ProgressListener() {
|
||||
@Override
|
||||
public void onProgress(Event event) {
|
||||
if (event.type.equals(UpdateService.EVENT_COMPLETE_AND_SAME) ||
|
||||
event.type.equals(UpdateService.EVENT_COMPLETE_WITH_CHANGES)) {
|
||||
// No need to prompt to update any more, we just
|
||||
// did it!
|
||||
changed = false;
|
||||
}
|
||||
|
||||
if (event.type.equals(UpdateService.EVENT_FINISHED)) {
|
||||
updateHandler = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void scanForRepos() {
|
||||
final RepoScanListAdapter adapter = new RepoScanListAdapter(this);
|
||||
final MDnsHelper mDnsHelper = new MDnsHelper(this, adapter);
|
||||
|
||||
final View view = getLayoutInflater().inflate(R.layout.repodiscoverylist, null);
|
||||
final ListView repoScanList = (ListView) view.findViewById(R.id.reposcanlist);
|
||||
|
||||
final AlertDialog alrt = new AlertDialog.Builder(this).setView(view)
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mDnsHelper.stopDiscovery();
|
||||
dialog.dismiss();
|
||||
}
|
||||
}).create();
|
||||
|
||||
alrt.setTitle(R.string.local_repos_title);
|
||||
alrt.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
mDnsHelper.stopDiscovery();
|
||||
}
|
||||
});
|
||||
|
||||
repoScanList.setAdapter(adapter);
|
||||
repoScanList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, final View view,
|
||||
int position, long id) {
|
||||
|
||||
final DiscoveredRepo discoveredService =
|
||||
(DiscoveredRepo) parent.getItemAtPosition(position);
|
||||
|
||||
final ServiceInfo serviceInfo = discoveredService.getServiceInfo();
|
||||
String type = serviceInfo.getPropertyString("type");
|
||||
String protocol = type.contains("fdroidrepos") ? "https:/" : "http:/";
|
||||
String path = serviceInfo.getPropertyString("path");
|
||||
if (TextUtils.isEmpty(path))
|
||||
path = "/fdroid/repo";
|
||||
String serviceUrl = protocol + serviceInfo.getInetAddresses()[0]
|
||||
+ ":" + serviceInfo.getPort() + path;
|
||||
showAddRepo(serviceUrl, serviceInfo.getPropertyString("fingerprint"));
|
||||
}
|
||||
});
|
||||
|
||||
alrt.show();
|
||||
mDnsHelper.discoverServices();
|
||||
}
|
||||
|
||||
public void importRepo(String uri, String fingerprint) {
|
||||
isImportingRepo = true;
|
||||
showAddRepo(uri, fingerprint);
|
||||
}
|
||||
|
||||
private void showAddRepo() {
|
||||
/*
|
||||
* If there is text in the clipboard, and it looks like a URL, use that.
|
||||
* Otherwise use "https://" as default repo string.
|
||||
*/
|
||||
ClipboardCompat clipboard = ClipboardCompat.create(this);
|
||||
String text = clipboard.getText();
|
||||
String fingerprint = null;
|
||||
if (!TextUtils.isEmpty(text)) {
|
||||
try {
|
||||
new URL(text);
|
||||
Uri uri = Uri.parse(text);
|
||||
fingerprint = uri.getQueryParameter("fingerprint");
|
||||
// uri might contain a QR-style, all uppercase URL:
|
||||
if (TextUtils.isEmpty(fingerprint))
|
||||
fingerprint = uri.getQueryParameter("FINGERPRINT");
|
||||
text = NewRepoConfig.sanitizeRepoUri(uri);
|
||||
} catch (MalformedURLException e) {
|
||||
text = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
text = DEFAULT_NEW_REPO_TEXT;
|
||||
}
|
||||
showAddRepo(text, fingerprint);
|
||||
}
|
||||
|
||||
private void showAddRepo(String newAddress, String newFingerprint) {
|
||||
final View view = getLayoutInflater().inflate(R.layout.addrepo, null);
|
||||
addRepoDialog = new AlertDialog.Builder(this).setView(view).create();
|
||||
final EditText uriEditText = (EditText) view.findViewById(R.id.edit_uri);
|
||||
final EditText fingerprintEditText = (EditText) view
|
||||
.findViewById(R.id.edit_fingerprint);
|
||||
|
||||
/*
|
||||
* If the "add new repo" dialog is launched by an action outside of
|
||||
* FDroid, i.e. a URL, then check to see if any existing repos match,
|
||||
* and change the action accordingly.
|
||||
*/
|
||||
final Repo repo = (newAddress != null && isImportingRepo)
|
||||
? RepoProvider.Helper.findByAddress(this, newAddress)
|
||||
: null;
|
||||
|
||||
addRepoDialog.setIcon(android.R.drawable.ic_menu_add);
|
||||
addRepoDialog.setTitle(getString(R.string.repo_add_title));
|
||||
addRepoDialog.setButton(DialogInterface.BUTTON_POSITIVE,
|
||||
getString(R.string.repo_add_add),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
String fp = fingerprintEditText.getText().toString();
|
||||
|
||||
// the DB uses null for no fingerprint but the above
|
||||
// code returns "" rather than null if its blank
|
||||
if (fp.equals(""))
|
||||
fp = null;
|
||||
|
||||
if (positiveAction == PositiveAction.ADD_NEW)
|
||||
createNewRepo(uriEditText.getText().toString(), fp);
|
||||
else if (positiveAction == PositiveAction.ENABLE)
|
||||
createNewRepo(repo);
|
||||
}
|
||||
});
|
||||
|
||||
addRepoDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
|
||||
getString(R.string.cancel),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
addRepoDialog.show();
|
||||
|
||||
final TextView overwriteMessage = (TextView) view.findViewById(R.id.overwrite_message);
|
||||
overwriteMessage.setVisibility(View.GONE);
|
||||
if (repo == null) {
|
||||
// no existing repo, add based on what we have
|
||||
positiveAction = PositiveAction.ADD_NEW;
|
||||
} else {
|
||||
// found the address in the DB of existing repos
|
||||
final Button addButton = addRepoDialog.getButton(DialogInterface.BUTTON_POSITIVE);
|
||||
addRepoDialog.setTitle(R.string.repo_exists);
|
||||
overwriteMessage.setVisibility(View.VISIBLE);
|
||||
if (newFingerprint != null)
|
||||
newFingerprint = newFingerprint.toUpperCase(Locale.ENGLISH);
|
||||
if (repo.fingerprint == null && newFingerprint != null) {
|
||||
// we're upgrading from unsigned to signed repo
|
||||
overwriteMessage.setText(R.string.repo_exists_add_fingerprint);
|
||||
addButton.setText(R.string.add_key);
|
||||
positiveAction = PositiveAction.ADD_NEW;
|
||||
} else if (newFingerprint == null || newFingerprint.equals(repo.fingerprint)) {
|
||||
// this entry already exists and is not enabled, offer to
|
||||
// enable
|
||||
// it
|
||||
if (repo.inuse) {
|
||||
addRepoDialog.dismiss();
|
||||
Toast.makeText(this, R.string.repo_exists_and_enabled,
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
} else {
|
||||
overwriteMessage.setText(R.string.repo_exists_enable);
|
||||
addButton.setText(R.string.enable);
|
||||
positiveAction = PositiveAction.ENABLE;
|
||||
}
|
||||
} else {
|
||||
// same address with different fingerprint, this could be
|
||||
// malicious, so force the user to manually delete the repo
|
||||
// before adding this one
|
||||
overwriteMessage.setTextColor(getResources().getColor(R.color.red));
|
||||
overwriteMessage.setText(R.string.repo_delete_to_overwrite);
|
||||
addButton.setText(R.string.overwrite);
|
||||
addButton.setEnabled(false);
|
||||
positiveAction = PositiveAction.IGNORE;
|
||||
}
|
||||
}
|
||||
|
||||
if (newFingerprint != null)
|
||||
fingerprintEditText.setText(newFingerprint);
|
||||
|
||||
if (newAddress != null) {
|
||||
// This trick of emptying text then appending,
|
||||
// rather than just setting in the first place,
|
||||
// is neccesary to move the cursor to the end of the input.
|
||||
uriEditText.setText("");
|
||||
uriEditText.append(newAddress);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new repo to the database.
|
||||
*/
|
||||
private void createNewRepo(String address, String fingerprint) {
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(RepoProvider.DataColumns.ADDRESS, address);
|
||||
if (fingerprint != null && fingerprint.length() > 0) {
|
||||
values.put(RepoProvider.DataColumns.FINGERPRINT,
|
||||
fingerprint.toUpperCase(Locale.ENGLISH));
|
||||
}
|
||||
RepoProvider.Helper.insert(this, values);
|
||||
finishedAddingRepo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeing as this repo already exists, we will force it to be enabled again.
|
||||
*/
|
||||
private void createNewRepo(Repo repo) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(RepoProvider.DataColumns.IN_USE, 1);
|
||||
RepoProvider.Helper.update(this, repo, values);
|
||||
repo.inuse = true;
|
||||
finishedAddingRepo();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* to reflect the newly created repo.
|
||||
*/
|
||||
private void finishedAddingRepo() {
|
||||
changed = true;
|
||||
addRepoDialog = null;
|
||||
if (isImportingRepo) {
|
||||
setResult(Activity.RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void addRepoFromIntent(Intent intent) {
|
||||
/* an URL from a click, NFC, QRCode scan, etc */
|
||||
NewRepoConfig newRepoConfig = new NewRepoConfig(this, intent);
|
||||
if (newRepoConfig.isValidRepo()) {
|
||||
importRepo(newRepoConfig.getUriString(), newRepoConfig.getFingerprint());
|
||||
checkIfNewRepoOnSameWifi(newRepoConfig);
|
||||
} else if (newRepoConfig.getErrorMessage() != null) {
|
||||
Toast.makeText(this, newRepoConfig.getErrorMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkIfNewRepoOnSameWifi(NewRepoConfig newRepo) {
|
||||
// if this is a local repo, check we're on the same wifi
|
||||
if (!TextUtils.isEmpty(newRepo.getBssid())) {
|
||||
WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
|
||||
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
|
||||
String bssid = wifiInfo.getBSSID().toLowerCase(Locale.ENGLISH);
|
||||
String newRepoBssid = Uri.decode(newRepo.getBssid()).toLowerCase(Locale.ENGLISH);
|
||||
if (!bssid.equals(newRepoBssid)) {
|
||||
String msg = String.format(getString(R.string.not_on_same_wifi), newRepo.getSsid());
|
||||
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
// TODO we should help the user to the right thing here,
|
||||
// instead of just showing a message!
|
||||
}
|
||||
}
|
||||
|
||||
public static class RepoListFragment extends ListFragment
|
||||
implements LoaderManager.LoaderCallbacks<Cursor>, RepoAdapter.EnabledListener {
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||
Uri uri = RepoProvider.getContentUri();
|
||||
Log.i("FDroid", "Creating repo loader '" + uri + "'.");
|
||||
String[] projection = new String[] {
|
||||
RepoProvider.DataColumns._ID,
|
||||
RepoProvider.DataColumns.NAME,
|
||||
RepoProvider.DataColumns.PUBLIC_KEY,
|
||||
RepoProvider.DataColumns.FINGERPRINT,
|
||||
RepoProvider.DataColumns.IN_USE
|
||||
};
|
||||
return new CursorLoader(getActivity(), uri, projection, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
|
||||
repoAdapter.swapCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> cursorLoader) {
|
||||
repoAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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
|
||||
* press the home button, or edit a repos details? It will start to
|
||||
* become somewhat-random as to when the actual enabling, disabling is
|
||||
* performed. So now, it just does the disable as soon as the user
|
||||
* clicks "Off" and then removes the apps. To compensate for the removal
|
||||
* of apps from index, it notifies the user via a toast that the apps
|
||||
* have been removed. Also, as before, it will still prompt the user to
|
||||
* update the repos if you toggled on on.
|
||||
*/
|
||||
@Override
|
||||
public void onSetEnabled(Repo repo, boolean isEnabled) {
|
||||
if (repo.inuse != isEnabled) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(RepoProvider.DataColumns.IN_USE, isEnabled ? 1 : 0);
|
||||
RepoProvider.Helper.update(getActivity(), repo, values);
|
||||
|
||||
if (isEnabled) {
|
||||
changed = true;
|
||||
} else {
|
||||
FDroidApp app = (FDroidApp) getActivity().getApplication();
|
||||
RepoProvider.Helper.purgeApps(getActivity(), repo, app);
|
||||
String notification = getString(R.string.repo_disabled_notification, repo.name);
|
||||
Toast.makeText(getActivity(), notification, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RepoAdapter repoAdapter;
|
||||
|
||||
private View createHeaderView() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
TextView textLastUpdate = new TextView(getActivity());
|
||||
long lastUpdate = prefs.getLong(Preferences.PREF_UPD_LAST, 0);
|
||||
String lastUpdateCheck = "";
|
||||
if (lastUpdate == 0) {
|
||||
lastUpdateCheck = getString(R.string.never);
|
||||
} else {
|
||||
Date d = new Date(lastUpdate);
|
||||
lastUpdateCheck = DateFormat.getDateFormat(getActivity()).format(d) +
|
||||
" " + DateFormat.getTimeFormat(getActivity()).format(d);
|
||||
}
|
||||
textLastUpdate.setText(getString(R.string.last_update_check, lastUpdateCheck));
|
||||
|
||||
int sidePadding = (int) getResources().getDimension(R.dimen.padding_side);
|
||||
int topPadding = (int) getResources().getDimension(R.dimen.padding_top);
|
||||
|
||||
textLastUpdate.setPadding(sidePadding, topPadding, sidePadding, topPadding);
|
||||
return textLastUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
if (getListAdapter() == null) {
|
||||
/*
|
||||
* Can't do this in the onCreate view, because "onCreateView"
|
||||
* which returns the list view is "called between onCreate and
|
||||
* onActivityCreated" according to the docs.
|
||||
*/
|
||||
getListView().addHeaderView(createHeaderView(), null, false);
|
||||
|
||||
/*
|
||||
* This could go in onCreate (and used to) but it needs to be
|
||||
* called after addHeaderView, which can only be called after
|
||||
* onCreate...
|
||||
*/
|
||||
repoAdapter = new RepoAdapter(getActivity(), null);
|
||||
repoAdapter.setEnabledListener(this);
|
||||
setListAdapter(repoAdapter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// Starts a new or restarts an existing Loader in this manager
|
||||
getLoaderManager().restartLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
|
||||
super.onListItemClick(l, v, position, id);
|
||||
|
||||
Repo repo = new Repo((Cursor) getListView().getItemAtPosition(position));
|
||||
editRepo(repo);
|
||||
}
|
||||
|
||||
public static final int SHOW_REPO_DETAILS = 1;
|
||||
|
||||
public void editRepo(Repo repo) {
|
||||
Intent intent = new Intent(getActivity(), RepoDetailsActivity.class);
|
||||
intent.putExtra(RepoDetailsFragment.ARG_REPO_ID, repo.getId());
|
||||
startActivityForResult(intent, SHOW_REPO_DETAILS);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,11 +5,12 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.PreferencesActivity;
|
||||
import org.fdroid.fdroid.R;
|
||||
@ -51,10 +52,6 @@ public class SelectLocalAppsActivity extends ActionBarActivity {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return true;
|
||||
case R.id.action_search:
|
||||
SearchView searchView = (SearchView) item.getActionView();
|
||||
searchView.setIconified(false);
|
||||
return true;
|
||||
case R.id.action_settings:
|
||||
startActivity(new Intent(this, PreferencesActivity.class));
|
||||
return true;
|
||||
@ -68,7 +65,6 @@ public class SelectLocalAppsActivity extends ActionBarActivity {
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
MenuInflater inflater = mode.getMenuInflater();
|
||||
inflater.inflate(R.menu.select_local_apps_action_mode, menu);
|
||||
menu.findItem(R.id.action_search).setActionView(searchView);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,504 +0,0 @@
|
||||
|
||||
package org.fdroid.fdroid.views.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ContentValues;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
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;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
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.RepoAdapter;
|
||||
import org.fdroid.fdroid.views.RepoDetailsActivity;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public class RepoListFragment extends ListFragment
|
||||
implements LoaderManager.LoaderCallbacks<Cursor>, RepoAdapter.EnabledListener {
|
||||
|
||||
private AlertDialog addRepoDialog;
|
||||
private static final String DEFAULT_NEW_REPO_TEXT = "https://";
|
||||
private final int ADD_REPO = 1;
|
||||
private final int UPDATE_REPOS = 2;
|
||||
private final int SCAN_FOR_REPOS = 3;
|
||||
|
||||
private UpdateService.UpdateReceiver updateHandler = null;
|
||||
|
||||
public boolean hasChanged() {
|
||||
return changed;
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||
Uri uri = RepoProvider.getContentUri();
|
||||
Log.i("FDroid", "Creating repo loader '" + uri + "'.");
|
||||
String[] projection = new String[] {
|
||||
RepoProvider.DataColumns._ID,
|
||||
RepoProvider.DataColumns.NAME,
|
||||
RepoProvider.DataColumns.PUBLIC_KEY,
|
||||
RepoProvider.DataColumns.FINGERPRINT,
|
||||
RepoProvider.DataColumns.IN_USE
|
||||
};
|
||||
return new CursorLoader(getActivity(), uri, projection, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
|
||||
repoAdapter.swapCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> cursorLoader) {
|
||||
repoAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 press the home button, or edit a
|
||||
* repos details? It will start to become somewhat-random as to when the
|
||||
* actual enabling, disabling is performed. So now, it just does the disable
|
||||
* as soon as the user clicks "Off" and then removes the apps. To compensate
|
||||
* for the removal of apps from index, it notifies the user via a toast that
|
||||
* the apps have been removed. Also, as before, it will still prompt the
|
||||
* user to update the repos if you toggled on on.
|
||||
*/
|
||||
@Override
|
||||
public void onSetEnabled(Repo repo, boolean isEnabled) {
|
||||
if (repo.inuse != isEnabled) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(RepoProvider.DataColumns.IN_USE, isEnabled ? 1 : 0);
|
||||
RepoProvider.Helper.update(getActivity(), repo, values);
|
||||
|
||||
if (isEnabled) {
|
||||
changed = true;
|
||||
} else {
|
||||
FDroidApp app = (FDroidApp) getActivity().getApplication();
|
||||
RepoProvider.Helper.purgeApps(getActivity(), repo, app);
|
||||
String notification = getString(R.string.repo_disabled_notification, repo.name);
|
||||
Toast.makeText(getActivity(), notification, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum PositiveAction {
|
||||
ADD_NEW, ENABLE, IGNORE
|
||||
}
|
||||
|
||||
private PositiveAction positiveAction;
|
||||
|
||||
private boolean changed = false;
|
||||
|
||||
private RepoAdapter repoAdapter;
|
||||
|
||||
/**
|
||||
* True if activity started with an intent such as from QR code. False if
|
||||
* opened from, e.g. the main menu.
|
||||
*/
|
||||
private boolean isImportingRepo = false;
|
||||
|
||||
private View createHeaderView() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
TextView textLastUpdate = new TextView(getActivity());
|
||||
long lastUpdate = prefs.getLong(Preferences.PREF_UPD_LAST, 0);
|
||||
String lastUpdateCheck = "";
|
||||
if (lastUpdate == 0) {
|
||||
lastUpdateCheck = getString(R.string.never);
|
||||
} else {
|
||||
Date d = new Date(lastUpdate);
|
||||
lastUpdateCheck = DateFormat.getDateFormat(getActivity()).format(d) +
|
||||
" " + DateFormat.getTimeFormat(getActivity()).format(d);
|
||||
}
|
||||
textLastUpdate.setText(getString(R.string.last_update_check, lastUpdateCheck));
|
||||
|
||||
int sidePadding = (int) getResources().getDimension(R.dimen.padding_side);
|
||||
int topPadding = (int) getResources().getDimension(R.dimen.padding_top);
|
||||
|
||||
textLastUpdate.setPadding(sidePadding, topPadding, sidePadding, topPadding);
|
||||
return textLastUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
if (getListAdapter() == null) {
|
||||
// Can't do this in the onCreate view, because "onCreateView" which
|
||||
// returns the list view is "called between onCreate and
|
||||
// onActivityCreated" according to the docs.
|
||||
getListView().addHeaderView(createHeaderView(), null, false);
|
||||
|
||||
// This could go in onCreate (and used to) but it needs to be called
|
||||
// after addHeaderView, which can only be called after onCreate...
|
||||
repoAdapter = new RepoAdapter(getActivity(), null);
|
||||
repoAdapter.setEnabledListener(this);
|
||||
setListAdapter(repoAdapter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// Starts a new or restarts an existing Loader in this manager
|
||||
getLoaderManager().restartLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
|
||||
super.onListItemClick(l, v, position, id);
|
||||
|
||||
Repo repo = new Repo((Cursor) getListView().getItemAtPosition(position));
|
||||
editRepo(repo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
|
||||
MenuItem updateItem = menu.add(Menu.NONE, UPDATE_REPOS, 1,
|
||||
R.string.menu_update_repo).setIcon(R.drawable.ic_menu_refresh);
|
||||
MenuItemCompat.setShowAsAction(updateItem,
|
||||
MenuItemCompat.SHOW_AS_ACTION_ALWAYS |
|
||||
MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
|
||||
|
||||
MenuItem addItem = menu.add(Menu.NONE, ADD_REPO, 1, R.string.menu_add_repo).setIcon(
|
||||
android.R.drawable.ic_menu_add);
|
||||
MenuItemCompat.setShowAsAction(addItem,
|
||||
MenuItemCompat.SHOW_AS_ACTION_ALWAYS |
|
||||
MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
menu.add(Menu.NONE, SCAN_FOR_REPOS, 1, R.string.menu_scan_repo).setIcon(
|
||||
android.R.drawable.ic_menu_search);
|
||||
}
|
||||
}
|
||||
|
||||
public static final int SHOW_REPO_DETAILS = 1;
|
||||
|
||||
public void editRepo(Repo repo) {
|
||||
Intent intent = new Intent(getActivity(), RepoDetailsActivity.class);
|
||||
intent.putExtra(RepoDetailsFragment.ARG_REPO_ID, repo.getId());
|
||||
startActivityForResult(intent, SHOW_REPO_DETAILS);
|
||||
}
|
||||
|
||||
private void updateRepos() {
|
||||
updateHandler = UpdateService.updateNow(getActivity()).setListener(new ProgressListener() {
|
||||
@Override
|
||||
public void onProgress(Event event) {
|
||||
if (event.type.equals(UpdateService.EVENT_COMPLETE_AND_SAME) ||
|
||||
event.type.equals(UpdateService.EVENT_COMPLETE_WITH_CHANGES)) {
|
||||
// No need to prompt to update any more, we just did it!
|
||||
changed = false;
|
||||
}
|
||||
|
||||
if (event.type.equals(UpdateService.EVENT_FINISHED)) {
|
||||
updateHandler = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void scanForRepos() {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
final RepoScanListAdapter adapter = new RepoScanListAdapter(activity);
|
||||
final MDnsHelper mDnsHelper = new MDnsHelper(activity, adapter);
|
||||
|
||||
final View view = getLayoutInflater(null).inflate(R.layout.repodiscoverylist, null);
|
||||
final ListView repoScanList = (ListView) view.findViewById(R.id.reposcanlist);
|
||||
|
||||
final AlertDialog alrt = new AlertDialog.Builder(getActivity()).setView(view)
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mDnsHelper.stopDiscovery();
|
||||
dialog.dismiss();
|
||||
}
|
||||
}).create();
|
||||
|
||||
alrt.setTitle(R.string.local_repos_title);
|
||||
alrt.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
mDnsHelper.stopDiscovery();
|
||||
}
|
||||
});
|
||||
|
||||
repoScanList.setAdapter(adapter);
|
||||
repoScanList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, final View view,
|
||||
int position, long id) {
|
||||
|
||||
final DiscoveredRepo discoveredService =
|
||||
(DiscoveredRepo) parent.getItemAtPosition(position);
|
||||
|
||||
final ServiceInfo serviceInfo = discoveredService.getServiceInfo();
|
||||
String type = serviceInfo.getPropertyString("type");
|
||||
String protocol = type.contains("fdroidrepos") ? "https:/" : "http:/";
|
||||
String path = serviceInfo.getPropertyString("path");
|
||||
if (TextUtils.isEmpty(path))
|
||||
path = "/fdroid/repo";
|
||||
String serviceUrl = protocol + serviceInfo.getInetAddresses()[0]
|
||||
+ ":" + serviceInfo.getPort() + path;
|
||||
showAddRepo(serviceUrl, serviceInfo.getPropertyString("fingerprint"));
|
||||
}
|
||||
});
|
||||
|
||||
alrt.show();
|
||||
mDnsHelper.discoverServices();
|
||||
}
|
||||
|
||||
public void importRepo(String uri, String fingerprint) {
|
||||
isImportingRepo = true;
|
||||
showAddRepo(uri, fingerprint);
|
||||
}
|
||||
|
||||
private void showAddRepo() {
|
||||
showAddRepo(getNewRepoUri(), null);
|
||||
}
|
||||
|
||||
private void showAddRepo(String newAddress, String newFingerprint) {
|
||||
View view = getLayoutInflater(null).inflate(R.layout.addrepo, null);
|
||||
addRepoDialog = new AlertDialog.Builder(getActivity()).setView(view).create();
|
||||
final EditText uriEditText = (EditText) view.findViewById(R.id.edit_uri);
|
||||
final EditText fingerprintEditText = (EditText) view.findViewById(R.id.edit_fingerprint);
|
||||
|
||||
/*
|
||||
* If the "add new repo" dialog is launched by an action outside of
|
||||
* FDroid, i.e. a URL, then check to see if any existing repos match,
|
||||
* and change the action accordingly.
|
||||
*/
|
||||
final Repo repo = (newAddress != null && isImportingRepo)
|
||||
? RepoProvider.Helper.findByAddress(getActivity(), newAddress)
|
||||
: null;
|
||||
|
||||
addRepoDialog.setIcon(android.R.drawable.ic_menu_add);
|
||||
addRepoDialog.setTitle(getString(R.string.repo_add_title));
|
||||
addRepoDialog.setButton(DialogInterface.BUTTON_POSITIVE,
|
||||
getString(R.string.repo_add_add),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
String fp = fingerprintEditText.getText().toString();
|
||||
|
||||
// the DB uses null for no fingerprint but the above
|
||||
// code returns "" rather than null if its blank
|
||||
if (fp.equals(""))
|
||||
fp = null;
|
||||
|
||||
if (positiveAction == PositiveAction.ADD_NEW)
|
||||
createNewRepo(uriEditText.getText().toString(), fp);
|
||||
else if (positiveAction == PositiveAction.ENABLE)
|
||||
createNewRepo(repo);
|
||||
}
|
||||
});
|
||||
|
||||
addRepoDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
|
||||
getString(R.string.cancel),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
addRepoDialog.show();
|
||||
|
||||
final TextView overwriteMessage = (TextView) view.findViewById(R.id.overwrite_message);
|
||||
overwriteMessage.setVisibility(View.GONE);
|
||||
if (repo == null) {
|
||||
// no existing repo, add based on what we have
|
||||
positiveAction = PositiveAction.ADD_NEW;
|
||||
} else {
|
||||
// found the address in the DB of existing repos
|
||||
final Button addButton = addRepoDialog.getButton(DialogInterface.BUTTON_POSITIVE);
|
||||
addRepoDialog.setTitle(R.string.repo_exists);
|
||||
overwriteMessage.setVisibility(View.VISIBLE);
|
||||
if (newFingerprint != null)
|
||||
newFingerprint = newFingerprint.toUpperCase(Locale.ENGLISH);
|
||||
if (repo.fingerprint == null && newFingerprint != null) {
|
||||
// we're upgrading from unsigned to signed repo
|
||||
overwriteMessage.setText(R.string.repo_exists_add_fingerprint);
|
||||
addButton.setText(R.string.add_key);
|
||||
positiveAction = PositiveAction.ADD_NEW;
|
||||
} else if (newFingerprint == null || newFingerprint.equals(repo.fingerprint)) {
|
||||
// this entry already exists and is not enabled, offer to enable
|
||||
// it
|
||||
if (repo.inuse) {
|
||||
addRepoDialog.dismiss();
|
||||
Toast.makeText(getActivity(), R.string.repo_exists_and_enabled,
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
} else {
|
||||
overwriteMessage.setText(R.string.repo_exists_enable);
|
||||
addButton.setText(R.string.enable);
|
||||
positiveAction = PositiveAction.ENABLE;
|
||||
}
|
||||
} else {
|
||||
// same address with different fingerprint, this could be
|
||||
// malicious, so force the user to manually delete the repo
|
||||
// before adding this one
|
||||
overwriteMessage.setTextColor(getResources().getColor(R.color.red));
|
||||
overwriteMessage.setText(R.string.repo_delete_to_overwrite);
|
||||
addButton.setText(R.string.overwrite);
|
||||
addButton.setEnabled(false);
|
||||
positiveAction = PositiveAction.IGNORE;
|
||||
}
|
||||
}
|
||||
|
||||
if (newFingerprint != null)
|
||||
fingerprintEditText.setText(newFingerprint);
|
||||
|
||||
if (newAddress != null) {
|
||||
// This trick of emptying text then appending,
|
||||
// rather than just setting in the first place,
|
||||
// is neccesary to move the cursor to the end of the input.
|
||||
uriEditText.setText("");
|
||||
uriEditText.append(newAddress);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new repo to the database.
|
||||
*/
|
||||
private void createNewRepo(String address, String fingerprint) {
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(RepoProvider.DataColumns.ADDRESS, address);
|
||||
if (fingerprint != null && fingerprint.length() > 0) {
|
||||
values.put(RepoProvider.DataColumns.FINGERPRINT,
|
||||
fingerprint.toUpperCase(Locale.ENGLISH));
|
||||
}
|
||||
RepoProvider.Helper.insert(getActivity(), values);
|
||||
finishedAddingRepo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeing as this repo already exists, we will force it to be enabled again.
|
||||
*/
|
||||
private void createNewRepo(Repo repo) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(RepoProvider.DataColumns.IN_USE, 1);
|
||||
RepoProvider.Helper.update(getActivity(), repo, values);
|
||||
repo.inuse = true;
|
||||
finishedAddingRepo();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* to reflect the newly created repo.
|
||||
*/
|
||||
private void finishedAddingRepo() {
|
||||
changed = true;
|
||||
addRepoDialog = null;
|
||||
if (isImportingRepo) {
|
||||
getActivity().setResult(Activity.RESULT_OK);
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
|
||||
if (item.getItemId() == ADD_REPO) {
|
||||
showAddRepo();
|
||||
return true;
|
||||
} else if (item.getItemId() == UPDATE_REPOS) {
|
||||
updateRepos();
|
||||
return true;
|
||||
} else if (item.getItemId() == SCAN_FOR_REPOS) {
|
||||
scanForRepos();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is text in the clipboard, and it looks like a URL, use that.
|
||||
* Otherwise return "https://".
|
||||
*/
|
||||
private String getNewRepoUri() {
|
||||
ClipboardCompat clipboard = ClipboardCompat.create(getActivity());
|
||||
String text = clipboard.getText();
|
||||
if (text != null) {
|
||||
try {
|
||||
new URL(text);
|
||||
} catch (MalformedURLException e) {
|
||||
text = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (text == null) {
|
||||
text = DEFAULT_NEW_REPO_TEXT;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
@ -23,16 +23,17 @@ import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.SimpleCursorAdapter;
|
||||
import android.support.v4.widget.SimpleCursorAdapter.ViewBinder;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.support.v7.widget.SearchView.OnQueryTextListener;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ActionMode;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SimpleCursorAdapter;
|
||||
import android.widget.SimpleCursorAdapter.ViewBinder;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
||||
@ -125,8 +126,8 @@ public class SelectLocalAppsFragment extends ListFragment
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
if (mActionMode == null)
|
||||
mActionMode = selectLocalAppsActivity
|
||||
.startActionMode(selectLocalAppsActivity.mActionModeCallback);
|
||||
Cursor c = (Cursor) l.getAdapter().getItem(position);
|
||||
.startSupportActionMode(selectLocalAppsActivity.mActionModeCallback);
|
||||
Cursor c = (Cursor) getListAdapter().getItem(position);
|
||||
String packageName = c.getString(c.getColumnIndex(DataColumns.APP_ID));
|
||||
if (FDroidApp.selectedApps.contains(packageName)) {
|
||||
FDroidApp.selectedApps.remove(packageName);
|
||||
@ -155,7 +156,8 @@ public class SelectLocalAppsFragment extends ListFragment
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||
((SimpleCursorAdapter) this.getListAdapter()).swapCursor(cursor);
|
||||
SimpleCursorAdapter adapter = (SimpleCursorAdapter) getListAdapter();
|
||||
adapter.swapCursor(cursor);
|
||||
|
||||
ListView listView = getListView();
|
||||
int count = listView.getCount();
|
||||
@ -183,7 +185,8 @@ public class SelectLocalAppsFragment extends ListFragment
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
((SimpleCursorAdapter) this.getListAdapter()).swapCursor(null);
|
||||
SimpleCursorAdapter adapter = (SimpleCursorAdapter) getListAdapter();
|
||||
adapter.swapCursor(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
BIN
test/assets/masterKeyIndex.jar
Normal file
BIN
test/assets/masterKeyIndex.jar
Normal file
Binary file not shown.
@ -12,7 +12,11 @@ import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.updater.RepoUpdater.UpdateException;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@TargetApi(8)
|
||||
public class SignedRepoUpdaterTest extends InstrumentationTestCase {
|
||||
@ -165,4 +169,18 @@ public class SignedRepoUpdaterTest extends InstrumentationTestCase {
|
||||
// success!
|
||||
}
|
||||
}
|
||||
|
||||
public void testExtractIndexFromMasterKeyIndexJar() {
|
||||
if (!testFilesDir.canWrite())
|
||||
return;
|
||||
// this is supposed to fail
|
||||
try {
|
||||
repoUpdater.getIndexFromFile(getTestFile("masterKeyIndex.jar"));
|
||||
fail();
|
||||
} catch (UpdateException e) {
|
||||
// success!
|
||||
} catch (SecurityException e) {
|
||||
// success!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user