diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails2.java b/app/src/main/java/org/fdroid/fdroid/AppDetails2.java index 55c2ac73f..4917be1c1 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppDetails2.java +++ b/app/src/main/java/org/fdroid/fdroid/AppDetails2.java @@ -9,8 +9,10 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.database.ContentObserver; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CoordinatorLayout; import android.support.v4.content.LocalBroadcastManager; @@ -188,6 +190,13 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog if (app != null) { visiblePackageName = app.packageName; } + + appObserver = new AppObserver(new Handler()); + getContentResolver().registerContentObserver( + AppProvider.getHighestPriorityMetadataUri(app.packageName), + true, + appObserver); + updateNotificationsForApp(); } @@ -195,6 +204,8 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog super.onStop(); visiblePackageName = null; + getContentResolver().unregisterContentObserver(appObserver); + // When leaving the app details, make sure to refresh app status for this app, since // we might want to show notifications for it now. updateNotificationsForApp(); @@ -432,7 +443,9 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog case Installer.ACTION_INSTALL_COMPLETE: adapter.clearProgress(); unregisterInstallReceiver(); - onAppChanged(); + // Don't try and update the view here, because the InstalledAppProviderService + // hasn't had time to do its thing and mark the app as installed. Instead, we + // wait for that service to notify us, and then we will respond in appObserver. break; case Installer.ACTION_INSTALL_INTERRUPTED: adapter.clearProgress(); @@ -457,8 +470,14 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog unregisterInstallReceiver(); break; case Installer.ACTION_INSTALL_USER_INTERACTION: - PendingIntent installPendingIntent = - intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI); + Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK); + if (!isAppVisible(apk.packageName)) { + Utils.debugLog(TAG, "Ignore request for user interaction from installer, because " + apk.packageName + " is no longer showing."); + break; + } + + Utils.debugLog(TAG, "Automatically showing package manager for " + apk.packageName + " as it is being viewed by the user."); + PendingIntent installPendingIntent = intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI); try { installPendingIntent.send(); @@ -649,4 +668,26 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog } startUninstall(); } + + // observer to update view when package has been installed/deleted + private AppObserver appObserver; + + class AppObserver extends ContentObserver { + + AppObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + onChange(selfChange, null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + onAppChanged(); + } + + } + } diff --git a/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java b/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java index 55e3625d4..298f2e65d 100644 --- a/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java @@ -330,14 +330,13 @@ class NotificationHelper { App app = entry.app; AppUpdateStatusManager.Status status = entry.status; - int iconSmall = R.drawable.ic_launcher; Bitmap iconLarge = getLargeIconForEntry(entry); NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setAutoCancel(true) .setContentTitle(getSingleItemTitleString(app, status)) .setContentText(getSingleItemContentString(app, status)) - .setSmallIcon(iconSmall) + .setSmallIcon(R.drawable.ic_notification) .setColor(ContextCompat.getColor(context, R.color.fdroid_blue)) .setLargeIcon(iconLarge) .setLocalOnly(true) @@ -416,7 +415,7 @@ class NotificationHelper { NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setAutoCancel(!useStackedNotifications()) - .setSmallIcon(R.drawable.ic_launcher) + .setSmallIcon(R.drawable.ic_notification) .setColor(ContextCompat.getColor(context, R.color.fdroid_blue)) .setContentTitle(title) .setContentText(text) @@ -444,7 +443,7 @@ class NotificationHelper { new NotificationCompat.Builder(context) .setAutoCancel(true) .setLargeIcon(iconLarge) - .setSmallIcon(R.drawable.ic_launcher) + .setSmallIcon(R.drawable.ic_notification) .setColor(ContextCompat.getColor(context, R.color.fdroid_blue)) .setContentTitle(app.name) .setContentText(context.getString(R.string.notification_content_single_installed)) @@ -491,7 +490,7 @@ class NotificationHelper { NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setAutoCancel(!useStackedNotifications()) - .setSmallIcon(R.drawable.ic_launcher) + .setSmallIcon(R.drawable.ic_notification) .setColor(ContextCompat.getColor(context, R.color.fdroid_blue)) .setContentTitle(title) .setContentText(text) diff --git a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java index 6f0dee7fb..67c92743b 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java +++ b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java @@ -21,7 +21,6 @@ import java.io.FilenameFilter; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; import rx.functions.Action1; import rx.schedulers.Schedulers; @@ -64,7 +63,7 @@ public class InstalledAppProviderService extends IntentService { public void onCreate() { super.onCreate(); notifyEvents = PublishSubject.create(); - notifyEvents.debounce(1, TimeUnit.SECONDS) + notifyEvents .subscribeOn(Schedulers.newThread()) .subscribe(new Action1() { @Override diff --git a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java index a85b05672..94de54327 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java @@ -35,7 +35,6 @@ 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.BuildConfig; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; @@ -413,15 +412,12 @@ public class AppDetailsRecyclerViewAdapter } else { lastUpdateView.setVisibility(View.GONE); } + Apk suggestedApk = getSuggestedApk(); - // TODO replace this whatsNew test code with what comes from suggestedApk once that exists - //if (suggestedApk == null || TextUtils.isEmpty(suggestedApk.whatsNew)) { + + // TODO populate whatsNew with suggestedApk.whatsNew once that exists String whatsNew = null; - if (BuildConfig.DEBUG) { - if (Math.random() > 0.5) { - whatsNew = "This section will contain the 'what's new' information for the apk.\n\n\t• Bug fixes."; - } - } + //noinspection ConstantConditions if (suggestedApk == null || TextUtils.isEmpty(whatsNew)) { whatsNewView.setVisibility(View.GONE); } else { @@ -431,7 +427,7 @@ public class AppDetailsRecyclerViewAdapter StringBuilder sbWhatsNew = new StringBuilder(); sbWhatsNew.append(whatsNewView.getContext().getString(R.string.details_new_in_version, suggestedApk.versionName).toUpperCase(locale)); sbWhatsNew.append("\n\n"); - sbWhatsNew.append("This section will contain the 'what's new' information for the apk.\n\n\t• Bug fixes."); + sbWhatsNew.append(whatsNew); whatsNewView.setText(sbWhatsNew); whatsNewView.setVisibility(View.VISIBLE); } diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListActivity.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListActivity.java index 2da8b69ce..991195d71 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListActivity.java @@ -121,7 +121,7 @@ public class AppListActivity extends AppCompatActivity implements LoaderManager. Schema.AppMetadataTable.Cols.ALL, null, null, - null + Schema.AppMetadataTable.Cols.NAME ); } diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java index d434582fa..08e349509 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java +++ b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java @@ -448,14 +448,15 @@ public class AppListItemController extends RecyclerView.ViewHolder { private final BroadcastReceiver onInstallAction = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (currentApp == null || installButton == null) { + Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK); + if (currentApp == null || !TextUtils.equals(apk.packageName, currentApp.packageName)) { return; } configureAppName(currentApp); + configureActionButton(currentApp); - Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK); - if (!TextUtils.equals(apk.packageName, currentApp.packageName)) { + if (installButton == null) { return; } diff --git a/app/src/main/java/org/fdroid/fdroid/views/categories/CategoryController.java b/app/src/main/java/org/fdroid/fdroid/views/categories/CategoryController.java index aeb65eedd..10e99162a 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/categories/CategoryController.java +++ b/app/src/main/java/org/fdroid/fdroid/views/categories/CategoryController.java @@ -153,7 +153,7 @@ public class CategoryController extends RecyclerView.ViewHolder implements Loade }, null, null, - null + Schema.AppMetadataTable.Cols.NAME ); } } diff --git a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java index 759ac2358..571125aee 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java @@ -5,13 +5,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.annotation.Nullable; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; @@ -32,9 +28,7 @@ import org.fdroid.fdroid.R; import org.fdroid.fdroid.UpdateService; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.compat.UriCompat; -import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.NewRepoConfig; -import org.fdroid.fdroid.data.Schema; import org.fdroid.fdroid.views.ManageReposActivity; import org.fdroid.fdroid.views.apps.AppListActivity; import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; @@ -53,8 +47,7 @@ import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; * When switching from one screen to the next, we stay within this activity. The new screen will * get inflated (if required) */ -public class MainActivity extends AppCompatActivity implements BottomNavigationBar.OnTabSelectedListener, - LoaderManager.LoaderCallbacks { +public class MainActivity extends AppCompatActivity implements BottomNavigationBar.OnTabSelectedListener { private static final String TAG = "MainActivity"; @@ -66,8 +59,6 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB private static final String STATE_SELECTED_MENU_ID = "selectedMenuId"; - private static final int LOADER_NUM_UPDATES = 1; - private static final int REQUEST_SWAP = 3; private RecyclerView pager; @@ -89,7 +80,7 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB pager.setLayoutManager(new NonScrollingHorizontalLayoutManager(this)); pager.setAdapter(adapter); - updatesBadge = new BadgeItem(); + updatesBadge = new BadgeItem().hide(false); bottomNavigation = (BottomNavigationBar) findViewById(R.id.bottom_navigation); bottomNavigation.setTabSelectedListener(this) @@ -105,8 +96,8 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB .initialise(); IntentFilter updateableAppsFilter = new IntentFilter(AppUpdateStatusManager.BROADCAST_APPSTATUS_LIST_CHANGED); + updateableAppsFilter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_CHANGED); LocalBroadcastManager.getInstance(this).registerReceiver(onUpdateableAppsChanged, updateableAppsFilter); - getSupportLoaderManager().initLoader(LOADER_NUM_UPDATES, null, this); if (savedInstanceState != null) { selectedMenuId = savedInstanceState.getInt(STATE_SELECTED_MENU_ID, R.id.whats_new); @@ -319,22 +310,6 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB } } - @Override - public Loader onCreateLoader(int id, Bundle args) { - Uri uri = AppProvider.getCanUpdateUri(); - String[] projection = new String[]{Schema.AppMetadataTable.Cols._COUNT}; - - return new CursorLoader(this, uri, projection, null, null, null); - } - - @Override - public void onLoadFinished(Loader loader, Cursor cursor) { - cursor.moveToFirst(); - int canUpdateCount = cursor.getInt(cursor.getColumnIndex(Schema.AppMetadataTable.Cols._COUNT)); - cursor.close(); - refreshUpdatesBadge(canUpdateCount); - } - private void refreshUpdatesBadge(int canUpdateCount) { if (canUpdateCount == 0) { updatesBadge.hide(true); @@ -344,11 +319,6 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB } } - @Override - public void onLoaderReset(Loader loader) { - - } - private static class NonScrollingHorizontalLayoutManager extends LinearLayoutManager { NonScrollingHorizontalLayoutManager(Context context) { super(context, LinearLayoutManager.HORIZONTAL, false); @@ -365,11 +335,43 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB } } + /** + * There are a bunch of reasons why we would get notified about app statuses. + * The ones we are interested in are those which would result in the "items requiring user interaction" + * to increase or decrease: + * * Bulk updates of ready-to-install-apps (relating to {@link org.fdroid.fdroid.AppUpdateStatusService}. + * * Change in status to: + * * {@link AppUpdateStatusManager.Status#ReadyToInstall} (Causes the count to go UP by one) + * * {@link AppUpdateStatusManager.Status#Installed} (Causes the count to go DOWN by one) + */ private final BroadcastReceiver onUpdateableAppsChanged = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (AppUpdateStatusManager.REASON_UPDATES_AVAILABLE.equals(intent.getStringExtra(AppUpdateStatusManager.EXTRA_REASON_FOR_CHANGE))) { - getSupportLoaderManager().restartLoader(LOADER_NUM_UPDATES, null, MainActivity.this); + boolean updateBadge = false; + + AppUpdateStatusManager manager = AppUpdateStatusManager.getInstance(context); + + if (AppUpdateStatusManager.BROADCAST_APPSTATUS_LIST_CHANGED.equals(intent.getAction()) && + AppUpdateStatusManager.REASON_READY_TO_INSTALL.equals(intent.getStringExtra(AppUpdateStatusManager.EXTRA_REASON_FOR_CHANGE))) { + updateBadge = true; + } + + // Check if we have moved into the ReadyToInstall or Installed state. + AppUpdateStatusManager.AppUpdateStatus status = manager.get(intent.getStringExtra(AppUpdateStatusManager.EXTRA_APK_URL)); + boolean isStatusChange = intent.getBooleanExtra(AppUpdateStatusManager.EXTRA_IS_STATUS_UPDATE, false); + if (isStatusChange && status != null && (status.status == AppUpdateStatusManager.Status.ReadyToInstall || status.status == AppUpdateStatusManager.Status.Installed)) { + updateBadge = true; + } + + if (updateBadge) { + int count = 0; + for (AppUpdateStatusManager.AppUpdateStatus s : manager.getAll()) { + if (s.status == AppUpdateStatusManager.Status.ReadyToInstall) { + count++; + } + } + + refreshUpdatesBadge(count); } } }; diff --git a/app/src/main/res/drawable/ic_notification.xml b/app/src/main/res/drawable/ic_notification.xml new file mode 100644 index 000000000..b34e2a5ff --- /dev/null +++ b/app/src/main/res/drawable/ic_notification.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/layout/app_card_featured.xml b/app/src/main/res/layout/app_card_featured.xml index a5d7b47e1..835b82f34 100644 --- a/app/src/main/res/layout/app_card_featured.xml +++ b/app/src/main/res/layout/app_card_featured.xml @@ -35,6 +35,8 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/header_height" + android:foreground="?attr/selectableItemBackground" + android:clickable="true" > + android:layout_height="wrap_content" + android:foreground="?attr/selectableItemBackground" + android:clickable="true"> + android:layout_height="220dp" + android:foreground="?attr/selectableItemBackground" + android:clickable="true"> + android:layout_height="wrap_content" + android:foreground="?attr/selectableItemBackground" + android:clickable="true"> + android:layout_height="wrap_content" + android:clickable="true" + android:background="?attr/selectableItemBackground"> diff --git a/app/src/main/res/layout/category_item.xml b/app/src/main/res/layout/category_item.xml index 4f6e717d8..3ad11f283 100644 --- a/app/src/main/res/layout/category_item.xml +++ b/app/src/main/res/layout/category_item.xml @@ -78,10 +78,6 @@ android:orientation="horizontal" android:paddingTop="@dimen/category_preview__app_list__padding__vertical" android:paddingBottom="@dimen/category_preview__app_list__padding__vertical" - android:clipToPadding="false" - android:layout_marginLeft="8dp" - android:layout_marginRight="8dp" - android:layout_marginStart="8dp" - android:layout_marginEnd="8dp" /> + android:clipToPadding="false" /> \ No newline at end of file diff --git a/app/src/main/res/layout/installed_app_list_item.xml b/app/src/main/res/layout/installed_app_list_item.xml index 764e202db..0ffc031ab 100644 --- a/app/src/main/res/layout/installed_app_list_item.xml +++ b/app/src/main/res/layout/installed_app_list_item.xml @@ -7,7 +7,9 @@ android:layout_height="wrap_content" android:padding="4dp" android:paddingTop="8dp" - android:paddingBottom="8dp"> + android:paddingBottom="8dp" + android:clickable="true" + android:background="?attr/selectableItemBackground">