/* * Materialize - Materialize all those not material * Copyright (C) 2015 XiNGRZ <xxx@oxo.ooo> * * 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, see <http://www.gnu.org/licenses/>. */ package ooo.oxo.apps.materialize; import android.content.Intent; import android.databinding.DataBindingUtil; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityOptionsCompat; import android.support.v7.widget.PopupMenu; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; import com.bumptech.glide.Glide; import com.jakewharton.rxbinding.widget.RxTextView; import com.trello.rxlifecycle.components.support.RxAppCompatActivity; import com.umeng.analytics.MobclickAgent; import java.io.FileNotFoundException; import ooo.oxo.apps.materialize.app.ActivityOptionsCompatCompat; import ooo.oxo.apps.materialize.databinding.MainActivityBinding; import ooo.oxo.apps.materialize.io.IconCacheManager; import ooo.oxo.apps.materialize.rx.RxPackageManager; import ooo.oxo.apps.materialize.util.LauncherUtil; import ooo.oxo.apps.materialize.util.UpdateUtil; import rx.Observable; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; public class MainActivity extends RxAppCompatActivity implements AppInfoAdapter.OnItemClickListener { private static final String TAG = "MainActivity"; private static final int REQUEST_MAKE_ICON = 1; private IconCacheManager iconCacheManager; private AppInfoAdapter apps; private SearchPanelController searchPanelController; /** * @return Observable with apps known in Material Design filtered out */ private static Observable.Transformer<AppInfo, AppInfo> filterGoodGuys() { return observable -> observable .filter(app -> !app.component.getPackageName().startsWith("com.android.")) .filter(app -> !app.component.getPackageName().startsWith("com.google.")) .filter(app -> !app.component.getPackageName().startsWith("org.cyanogenmod.")) .filter(app -> !app.component.getPackageName().startsWith("com.cyanogenmod.")) .filter(app -> !app.component.getPackageName().startsWith("me.xingrz.")) .filter(app -> !app.component.getPackageName().startsWith("ooo.oxo.")); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity); setSupportActionBar(binding.toolbar); searchPanelController = new SearchPanelController(binding.searchBar); SearchMatcher searchMatcher = new SearchMatcher(() -> searchPanelController.getKeyword().getText().toString()); binding.apps.setAdapter(apps = new AppInfoAdapter(this, Glide.with(this), searchMatcher, this)); RxTextView.afterTextChangeEvents(searchPanelController.getKeyword()) .compose(bindToLifecycle()) .subscribe(avoid -> { apps.data.applyFilter(); binding.apps.smoothScrollToPosition(0); }); iconCacheManager = new IconCacheManager(this); RxPackageManager .intentActivities(getPackageManager(), Intents.MAIN, 0) .map(resolve -> AppInfo.from(resolve.activityInfo, getPackageManager(), iconCacheManager)) .filter(app -> app != null) .filter(app -> !app.component.getPackageName().equals(BuildConfig.APPLICATION_ID)) .compose(filterGoodGuys()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnCompleted(() -> binding.apps.smoothScrollToPosition(0)) .subscribe(apps.data::addWithIndex); UpdateUtil.checkForUpdateAndPrompt(this); } @Override public void onItemClick(AppInfoAdapter.ViewHolder holder) { int index = holder.getLayoutPosition(); AppInfo app = apps.data.get(index); Intent intent = new Intent(this, AdjustActivity.class); intent.putExtra("index", index); intent.putExtra("activity", app.activityInfo); int y = Build.VERSION.SDK_INT >= 23 ? (int) (getResources().getDisplayMetrics().heightPixels * 0.16f) : 0; ActivityOptionsCompat options = ActivityOptionsCompatCompat.makeClipRevealAnimation( holder.itemView, 0, -y, holder.itemView.getWidth(), holder.itemView.getHeight()); ActivityCompat.startActivityForResult(this, intent, REQUEST_MAKE_ICON, options.toBundle()); } @Override public void onShowingPopupMenu(AppInfoAdapter.ViewHolder holder, PopupMenu menu) { AppInfo app = apps.data.get(holder.getLayoutPosition()); if (app.cache == null) { onItemClick(holder); } else { menu.show(); } } @Override public void onReinstallShortcutClick(AppInfoAdapter.ViewHolder holder) { AppInfo app = apps.data.get(holder.getLayoutPosition()); Observable.just(app) .compose(bindToLifecycle()) .filter(a -> a.cache != null) .observeOn(Schedulers.io()) .map(a -> decodeIconFromCache(a.cache)) .filter(icon -> icon != null) .observeOn(AndroidSchedulers.mainThread()) .subscribe(icon -> { LauncherUtil.installShortcut(this, app.getIntent(), app.label, icon); Toast.makeText(this, R.string.toast_added_to_home, Toast.LENGTH_SHORT).show(); MobclickAgent.onEvent(this, "install"); }); } @Nullable private Bitmap decodeIconFromCache(Uri uri) { try { return BitmapFactory.decodeStream(getContentResolver().openInputStream(uri)); } catch (FileNotFoundException e) { Log.e(TAG, "Failed reading icon cache from " + uri.toString(), e); return null; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_MAKE_ICON: if (data != null) { invalidateIcon(data.getIntExtra("index", 0)); } break; default: super.onActivityResult(requestCode, resultCode, data); } } private void invalidateIcon(int index) { AppInfo app = apps.data.get(index); if (app.resolveCache(iconCacheManager)) { app.cache = app.cache .buildUpon() .appendQueryParameter("t", String.valueOf(System.currentTimeMillis())) .build(); } apps.notifyItemChanged(index); } @Override protected void onResume() { super.onResume(); MobclickAgent.onResume(this); } @Override protected void onPause() { super.onPause(); MobclickAgent.onPause(this); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.about: startActivity(new Intent(this, AboutActivity.class)); return true; case R.id.search: searchPanelController.open(); return true; default: return super.onOptionsItemSelected(item); } } @Override public void onBackPressed() { if (!searchPanelController.onBackPressed()) { super.onBackPressed(); } } }