diff --git a/app/src/main/java/com/topjohnwu/magisk/BaseModuleFragment.java b/app/src/main/java/com/topjohnwu/magisk/BaseModuleFragment.java index 739ec0463..3becab3a3 100644 --- a/app/src/main/java/com/topjohnwu/magisk/BaseModuleFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/BaseModuleFragment.java @@ -1,25 +1,33 @@ package com.topjohnwu.magisk; +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; import android.support.v4.app.Fragment; -import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.RecyclerView; +import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.WindowManager; import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.TextView; import com.topjohnwu.magisk.module.Module; +import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.utils.Utils; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ExecutionException; import butterknife.BindView; import butterknife.ButterKnife; @@ -28,26 +36,24 @@ public abstract class BaseModuleFragment extends Fragment { @BindView(R.id.recyclerView) RecyclerView recyclerView; @BindView(R.id.empty_rv) TextView emptyTv; - private SwipeRefreshLayout mSwipeRefreshLayout; - private View view; + + private SharedPreferences prefs; + @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - view = inflater.inflate(R.layout.single_module_fragment, container, false); - mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipeRefreshLayout); - mSwipeRefreshLayout.setOnRefreshListener(() -> { - refreshItems(); - }); + View viewMain = inflater.inflate(R.layout.single_module_fragment, container, false); - ButterKnife.bind(this, view); + + ButterKnife.bind(this, viewMain); prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); prefs.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { if (s.contains("updated")) { - view.invalidate(); - view.requestLayout(); + viewMain.invalidate(); + viewMain.requestLayout(); } } @@ -56,7 +62,7 @@ public abstract class BaseModuleFragment extends Fragment { emptyTv.setVisibility(View.VISIBLE); recyclerView.setVisibility(View.GONE); - return view; + return viewMain; } recyclerView.setAdapter(new ModulesAdapter(listModules(), (chk, position) -> { @@ -81,25 +87,192 @@ public abstract class BaseModuleFragment extends Fragment { listModules().get(position).deleteRemoveFile(); Snackbar.make(undeleteBtn, R.string.remove_file_deleted, Snackbar.LENGTH_SHORT).show(); })); - return view; + return viewMain; } - void refreshItems() { - Log.d("Magisk", "Calling refreshitems for online"); - Utils.LoadModules utils = new Utils.LoadModules(getActivity(),true); - utils.execute(); - onItemsLoadComplete(); - view.requestLayout(); - } - - - void onItemsLoadComplete() { - // Update the adapter and notify data set changed - // ... - - // Stop refresh animation - mSwipeRefreshLayout.setRefreshing(false); - } protected abstract List listModules(); + + public class ModulesAdapter extends RecyclerView.Adapter { + + private final List mList; + List mExpandedList; + @BindView(R.id.expand_layout) + LinearLayout expandedLayout; + private View viewMain; + private Context context; + private final Utils.ItemClickListener chboxListener; + private final Utils.ItemClickListener deleteBtnListener; + private final Utils.ItemClickListener unDeleteBtnListener; + + public ModulesAdapter(List list, Utils.ItemClickListener chboxListener, Utils.ItemClickListener deleteBtnListener, Utils.ItemClickListener undeleteBtnListener) { + this.mList = list; + mExpandedList = new ArrayList<>(mList.size()); + for (int i = 0; i < mList.size(); i++) { + mExpandedList.add(false); + } + this.chboxListener = chboxListener; + this.deleteBtnListener = deleteBtnListener; + this.unDeleteBtnListener = undeleteBtnListener; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + viewMain = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_module, parent, false); + context = parent.getContext(); + ButterKnife.bind(this, viewMain); + return new ViewHolder(viewMain); + } + + @Override + public void onBindViewHolder(final ViewHolder holder, int position) { + final Module module = mList.get(position); + Log.d("Magisk","ModulesAdapter: Trying set up bindview from list pos " + position + " and " + module.getName() ); + + holder.title.setText(module.getName()); + holder.versionName.setText(module.getVersion()); + holder.description.setText(module.getDescription()); + + holder.checkBox.setChecked(module.isEnabled()); + holder.checkBox.setOnCheckedChangeListener((compoundButton, b) -> chboxListener.onItemClick(compoundButton, holder.getAdapterPosition())); + + holder.delete.setOnClickListener(view -> { + if (module.willBeRemoved()) { + unDeleteBtnListener.onItemClick(holder.delete, holder.getAdapterPosition()); + } else { + deleteBtnListener.onItemClick(holder.delete, holder.getAdapterPosition()); + } + + updateDeleteButton(holder, module); + }); + + updateDeleteButton(holder, module); + } + + private void updateDeleteButton(ViewHolder holder, Module module) { + holder.warning.setVisibility(module.willBeRemoved() ? View.VISIBLE : View.GONE); + + if (module.willBeRemoved()) { + holder.delete.setImageResource(R.drawable.ic_undelete); + } else { + holder.delete.setImageResource(R.drawable.ic_delete); + } + } + + @Override + public int getItemCount() { + return mList.size(); + } + + class ViewHolder extends RecyclerView.ViewHolder { + + @BindView(R.id.title) TextView title; + + @BindView(R.id.version_name) TextView versionName; + @BindView(R.id.description) TextView description; + + @BindView(R.id.warning) TextView warning; + + @BindView(R.id.checkbox) CheckBox checkBox; + @BindView(R.id.delete) + ImageView delete; + @BindView(R.id.expand_layout) + LinearLayout expandLayout; + private ValueAnimator mAnimator; + private int mMeasuredHeight; + + public ViewHolder(View itemView) { + super(itemView); + WindowManager windowmanager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + ButterKnife.bind(this, itemView); + DisplayMetrics dimension = new DisplayMetrics(); + windowmanager.getDefaultDisplay().getMetrics(dimension); + final int mHeight = dimension.heightPixels; + expandLayout.getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + + @Override + public boolean onPreDraw() { + expandLayout.getViewTreeObserver().removeOnPreDrawListener(this); + expandLayout.setVisibility(View.GONE); + + final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + final int heightSpec = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED); + expandLayout.measure(widthSpec, heightSpec); + mAnimator = slideAnimator(0, expandLayout.getMeasuredHeight()); + return true; + } + }); + + viewMain.setOnClickListener(view -> { + int position = getAdapterPosition(); + Log.d("Magisk", "BaseRepoFragment: CLICK. " + position + " and " + mExpandedList.get(position)); + + if (mExpandedList.get(position)) { + collapse(expandLayout); + } else { + expand(expandLayout); + } + mExpandedList.set(position, !mExpandedList.get(position)); + + }); + if (!Shell.rootAccess()) { + checkBox.setEnabled(false); + delete.setEnabled(false); + } + } + private void expand(View view) { + + // set Visible + + + Log.d("Magisk", "BaseRepoFragment: Expand anim called " + mMeasuredHeight + " and " + view.getId()); + view.setVisibility(View.VISIBLE); + mAnimator.start(); + } + + private void collapse(View view) { + int finalHeight = view.getHeight(); + ValueAnimator mAnimator = slideAnimator(finalHeight, 0); + Log.d("Magisk", "BaseRepoFragment: Collapse anim called " + finalHeight + " and " + view.getId()); + + mAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationEnd(Animator animator) { + // Height=0, but it set visibility to GONE + view.setVisibility(View.GONE); + } + + @Override + public void onAnimationStart(Animator animator) { + } + + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + }); + mAnimator.start(); + } + + private ValueAnimator slideAnimator(int start, int end) { + + ValueAnimator animator = ValueAnimator.ofInt(start, end); + + animator.addUpdateListener(valueAnimator -> { + // Update Height + int value = (Integer) valueAnimator.getAnimatedValue(); + + ViewGroup.LayoutParams layoutParams = expandLayout + .getLayoutParams(); + layoutParams.height = value; + expandLayout.setLayoutParams(layoutParams); + }); + return animator; + } + } + } } diff --git a/app/src/main/java/com/topjohnwu/magisk/BaseRepoFragment.java b/app/src/main/java/com/topjohnwu/magisk/BaseRepoFragment.java index a60bd1043..bdf96520d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/BaseRepoFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/BaseRepoFragment.java @@ -1,20 +1,33 @@ package com.topjohnwu.magisk; +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Color; import android.os.Bundle; +import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; -import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.RecyclerView; +import android.text.util.Linkify; +import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; -import com.topjohnwu.magisk.module.Module; import com.topjohnwu.magisk.module.Repo; import com.topjohnwu.magisk.utils.Utils; +import java.io.File; +import java.util.ArrayList; import java.util.List; import butterknife.BindView; @@ -26,15 +39,11 @@ public abstract class BaseRepoFragment extends Fragment { RecyclerView recyclerView; @BindView(R.id.empty_rv) TextView emptyTv; - private SwipeRefreshLayout mSwipeRefreshLayout; + @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.single_module_fragment, container, false); - mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipeRefreshLayout); - mSwipeRefreshLayout.setOnRefreshListener(() -> { - refreshItems(); - }); ButterKnife.bind(this, view); @@ -44,25 +53,234 @@ public abstract class BaseRepoFragment extends Fragment { return view; } - + Log.d("Magisk", "BaseRepoFragment: ListRepos size is " + listRepos().size()); recyclerView.setAdapter(new ReposAdapter(listRepos()) { }); return view; } - void refreshItems() { - Log.d("Magisk", "Calling refreshitems for online"); - new Utils.LoadModules(getActivity(),true).execute(); - onItemsLoadComplete(); - } - void onItemsLoadComplete() { - // Update the adapter and notify data set changed - // ... - - // Stop refresh animation - mSwipeRefreshLayout.setRefreshing(false); - } protected abstract List listRepos(); + + public class ReposAdapter extends RecyclerView.Adapter { + + private final List mList; + List mExpandedList; + @BindView(R.id.update) + ImageView updateImage; + @BindView(R.id.installed) + ImageView installedImage; + @BindView(R.id.expand_layout) + LinearLayout expandedLayout; + private View viewMain; + private Context context; + private boolean mIsInstalled, mCanUpdate; + + public ReposAdapter(List list) { + this.mList = list; + mExpandedList = new ArrayList<>(mList.size()); + for (int i = 0; i < mList.size(); i++) { + mExpandedList.add(false); + } + + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + viewMain = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_repo, parent, false); + ButterKnife.bind(this, viewMain); + context = parent.getContext(); + return new ViewHolder(viewMain); + } + + @Override + public void onBindViewHolder(final ViewHolder holder, int position) { + final Repo repo = mList.get(position); + + Log.d("Magisk", "ReposAdapter: Trying set up bindview from list pos " + position + " out of a total of " + mList.size() + " and " + repo.getId()); + if (repo.getId() != null) { + TextView authorView = holder.author; + holder.title.setText(repo.getName()); + holder.versionName.setText(repo.getmVersion()); + holder.description.setText(repo.getDescription()); + String authorString = getResources().getString(R.string.author) + " " + repo.getmAuthor(); + holder.author.setText(authorString); + if ((repo.getmLogUrl() != null) && (repo.getmLogUrl().equals(""))) { + holder.log.setText(repo.getmLogUrl()); + Linkify.addLinks(holder.log, Linkify.WEB_URLS); + } else { + holder.log.setVisibility(View.GONE); + } + holder.installedStatus.setText(repo.isInstalled() ? getResources().getString(R.string.module_installed) : getResources().getString(R.string.module_not_installed)); + if (mExpandedList.get(position)) { + holder.expandLayout.setVisibility(View.VISIBLE); + } else { + holder.expandLayout.setVisibility(View.GONE); + } + if (repo.isInstalled()) { + holder.installedStatus.setTextColor(Color.parseColor("#14AD00")); + holder.updateStatus.setText(repo.canUpdate() ? getResources().getString(R.string.module_update_available) : getResources().getString(R.string.module_up_to_date)); + } + Log.d("Magisk", "ReposAdapter: Setting up info " + repo.getId() + " and " + repo.getDescription() + " and " + repo.getmVersion()); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + updateImage.setImageResource(R.drawable.ic_system_update_alt_black); + mCanUpdate = prefs.getBoolean("repo-isInstalled_" + repo.getId(), false); + updateImage.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!mIsInstalled | mCanUpdate) { + + Utils.DownloadReceiver reciever = new Utils.DownloadReceiver() { + @Override + public void task(File file) { + Log.d("Magisk", "Task firing"); + new Utils.FlashZIP(context, repo.getId(), file.toString()).execute(); + } + }; + String filename = repo.getId().replace(" ", "") + ".zip"; + Utils.downloadAndReceive(context, reciever, repo.getmZipUrl(), filename); + } else { + Toast.makeText(context, repo.getId() + " is already installed.", Toast.LENGTH_SHORT).show(); + } + } + }); + if (prefs.contains("repo-isInstalled_" + repo.getId())) { + mIsInstalled = prefs.getBoolean("repo-isInstalled_" + repo.getId(), false); +// if (mIsInstalled) { +// installedImage.setImageResource(R.drawable.ic_done_black); +// } + + } + + + } + + + } + + + @Override + public int getItemCount() { + return mList.size(); + } + + class ViewHolder extends RecyclerView.ViewHolder { + + @BindView(R.id.title) + TextView title; + @BindView(R.id.version_name) + TextView versionName; + @BindView(R.id.description) + TextView description; + @BindView(R.id.author) + TextView author; + @BindView(R.id.log) + TextView log; + @BindView(R.id.installedStatus) + TextView installedStatus; + @BindView(R.id.updateStatus) + TextView updateStatus; + @BindView(R.id.expand_layout) + LinearLayout expandLayout; + private ValueAnimator mAnimator; + private int mMeasuredHeight; + + + public ViewHolder(View itemView) { + super(itemView); + WindowManager windowmanager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + ButterKnife.bind(this, itemView); + + DisplayMetrics dimension = new DisplayMetrics(); + windowmanager.getDefaultDisplay().getMetrics(dimension); + final int mHeight = dimension.heightPixels; + expandLayout.getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + + @Override + public boolean onPreDraw() { + expandLayout.getViewTreeObserver().removeOnPreDrawListener(this); + expandLayout.setVisibility(View.GONE); + + final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + expandLayout.measure(widthSpec, heightSpec); + mAnimator = slideAnimator(0, expandLayout.getMeasuredHeight()); + return true; + } + + }); + + viewMain.setOnClickListener(view -> { + int position = getAdapterPosition(); + Log.d("Magisk", "BaseRepoFragment: CLICK. " + position + " and " + mExpandedList.get(position)); + + if (mExpandedList.get(position)) { + collapse(expandLayout); + } else { + expand(expandLayout); + } + mExpandedList.set(position, !mExpandedList.get(position)); + + }); + + } + + private void expand(View view) { + + // set Visible + + + Log.d("Magisk", "BaseRepoFragment: Expand anim called " + mMeasuredHeight + " and " + view.getId()); + view.setVisibility(View.VISIBLE); + mAnimator.start(); + } + + private void collapse(View view) { + int finalHeight = view.getHeight(); + ValueAnimator mAnimator = slideAnimator(finalHeight, 0); + Log.d("Magisk", "BaseRepoFragment: Collapse anim called " + finalHeight + " and " + view.getId()); + + mAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationEnd(Animator animator) { + // Height=0, but it set visibility to GONE + view.setVisibility(View.GONE); + } + + @Override + public void onAnimationStart(Animator animator) { + } + + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + }); + mAnimator.start(); + } + + private ValueAnimator slideAnimator(int start, int end) { + + ValueAnimator animator = ValueAnimator.ofInt(start, end); + + animator.addUpdateListener(valueAnimator -> { + // Update Height + int value = (Integer) valueAnimator.getAnimatedValue(); + + ViewGroup.LayoutParams layoutParams = expandLayout + .getLayoutParams(); + layoutParams.height = value; + expandLayout.setLayoutParams(layoutParams); + }); + return animator; + } + + + } + } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ModulesAdapter.java b/app/src/main/java/com/topjohnwu/magisk/ModulesAdapter.java deleted file mode 100644 index c5b015f4c..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/ModulesAdapter.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.topjohnwu.magisk; - -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.TextView; - -import com.topjohnwu.magisk.module.Module; -import com.topjohnwu.magisk.utils.Shell; -import com.topjohnwu.magisk.utils.Utils; - -import java.util.List; - -import butterknife.BindView; -import butterknife.ButterKnife; - -public class ModulesAdapter extends RecyclerView.Adapter { - - private final List mList; - private final Utils.ItemClickListener chboxListener; - private final Utils.ItemClickListener deleteBtnListener; - private final Utils.ItemClickListener unDeleteBtnListener; - - public ModulesAdapter(List list, Utils.ItemClickListener chboxListener, Utils.ItemClickListener deleteBtnListener, Utils.ItemClickListener undeleteBtnListener) { - this.mList = list; - this.chboxListener = chboxListener; - this.deleteBtnListener = deleteBtnListener; - this.unDeleteBtnListener = undeleteBtnListener; - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_module, parent, false); - - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(final ViewHolder holder, int position) { - final Module module = mList.get(position); - - holder.title.setText(module.getName()); - holder.versionName.setText(module.getVersion()); - holder.description.setText(module.getDescription()); - - holder.checkBox.setChecked(module.isEnabled()); - holder.checkBox.setOnCheckedChangeListener((compoundButton, b) -> chboxListener.onItemClick(compoundButton, holder.getAdapterPosition())); - - holder.delete.setOnClickListener(view -> { - if (module.willBeRemoved()) { - unDeleteBtnListener.onItemClick(holder.delete, holder.getAdapterPosition()); - } else { - deleteBtnListener.onItemClick(holder.delete, holder.getAdapterPosition()); - } - - updateDeleteButton(holder, module); - }); - - updateDeleteButton(holder, module); - } - - private void updateDeleteButton(ViewHolder holder, Module module) { - holder.warning.setVisibility(module.willBeRemoved() ? View.VISIBLE : View.GONE); - - if (module.willBeRemoved()) { - holder.delete.setImageResource(R.drawable.ic_undelete); - } else { - holder.delete.setImageResource(R.drawable.ic_delete); - } - } - - @Override - public int getItemCount() { - return mList.size(); - } - - static class ViewHolder extends RecyclerView.ViewHolder { - - @BindView(R.id.title) TextView title; - - @BindView(R.id.version_name) TextView versionName; - @BindView(R.id.description) TextView description; - - @BindView(R.id.warning) TextView warning; - - @BindView(R.id.checkbox) CheckBox checkBox; - @BindView(R.id.delete) ImageView delete; - - public ViewHolder(View itemView) { - super(itemView); - ButterKnife.bind(this, itemView); - if (!Shell.rootAccess()) { - checkBox.setEnabled(false); - delete.setEnabled(false); - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java b/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java index 3ab89fcc1..4dd1c2ceb 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java @@ -14,6 +14,7 @@ import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; +import android.support.v4.widget.SwipeRefreshLayout; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -40,7 +41,7 @@ public class ModulesFragment extends Fragment { public static List listModulesCache = new ArrayList<>(); public static List listModulesDownload = new ArrayList<>(); private static final int FILE_SELECT_CODE = 0; - private File input; + private int viewPagePosition; @BindView(R.id.progressBar) ProgressBar progressBar; @BindView(R.id.fab) FloatingActionButton fabio; @@ -51,18 +52,16 @@ public class ModulesFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.modules_fragment, container, false); + ButterKnife.bind(this, view); + new Utils.LoadModules(getActivity(),false).execute(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); - if (prefs.contains("hasCachedRepos")) { - new Utils.LoadModules(getActivity(), false).execute(); - } else { - new Utils.LoadModules(getActivity(), true).execute(); - } new updateUI().execute(); setHasOptionsMenu(true); return view; } + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); @@ -100,13 +99,15 @@ public class ModulesFragment extends Fragment { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.force_reload: + viewPagePosition = tabLayout.getSelectedTabPosition(); listModules.clear(); listModulesCache.clear(); listModulesDownload.clear(); progressBar.setVisibility(View.VISIBLE); viewPager.setAdapter(new TabsAdapter(getChildFragmentManager())); tabLayout.setupWithViewPager(viewPager); - new Utils.LoadModules(getActivity(),false).execute(); + viewPager.setCurrentItem(viewPagePosition); + new Utils.LoadModules(getActivity(),true).execute(); new updateUI().execute(); break; } @@ -114,6 +115,11 @@ public class ModulesFragment extends Fragment { return super.onOptionsItemSelected(item); } + void selectPage(int pageIndex){ + tabLayout.setScrollPosition(pageIndex,0f,true); + viewPager.setCurrentItem(pageIndex); + } + public static class NormalModuleFragment extends BaseModuleFragment { @Override @@ -153,9 +159,10 @@ public class ModulesFragment extends Fragment { super.onPostExecute(v); progressBar.setVisibility(View.GONE); - viewPager.setAdapter(new TabsAdapter(getChildFragmentManager())); tabLayout.setupWithViewPager(viewPager); + selectPage(viewPagePosition); + } } @@ -185,7 +192,7 @@ public class ModulesFragment extends Fragment { return new NormalModuleFragment(); } else if (position == 1) { return new CacheModuleFragment(); - } else { + } else { return new DownloadModuleFragment(); } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ReposAdapter.java b/app/src/main/java/com/topjohnwu/magisk/ReposAdapter.java deleted file mode 100644 index f543e7d29..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/ReposAdapter.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.topjohnwu.magisk; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.support.v7.widget.RecyclerView; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import com.topjohnwu.magisk.module.Module; -import com.topjohnwu.magisk.module.Repo; -import com.topjohnwu.magisk.utils.Shell; -import com.topjohnwu.magisk.utils.Utils; - -import java.io.File; -import java.util.List; - -import butterknife.BindView; -import butterknife.ButterKnife; - -public class ReposAdapter extends RecyclerView.Adapter { - - private final List mList; - private View view; - private Context context; - - public ReposAdapter(List list) { - this.mList = list; - - } - - - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_repo, parent, false); - context = parent.getContext(); - - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(final ViewHolder holder, int position) { - final Repo repo = mList.get(position); - - holder.title.setText(repo.getName()); - holder.versionName.setText(repo.getVersion()); - holder.description.setText(repo.getDescription()); - view.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - if (!prefs.contains("isInstalled_" + repo.getName())) { - - Utils.DownloadReceiver reciever = new Utils.DownloadReceiver() { - @Override - public void task(File file) { - Log.d("Magisk", "Task firing"); - new Utils.FlashZIP(context, repo.getName(), file.toString()).execute(); - } - }; - String filename = repo.getName().replace(" ", "") + ".zip"; - Utils.downloadAndReceive(context, reciever, repo.getZipUrl(), filename); - } else { - Toast.makeText(context,repo.getName() + " is already installed.",Toast.LENGTH_SHORT).show(); - } - } - }); - - - } - - - - - @Override - public int getItemCount() { - return mList.size(); - } - - static class ViewHolder extends RecyclerView.ViewHolder { - - @BindView(R.id.title) TextView title; - - @BindView(R.id.version_name) TextView versionName; - @BindView(R.id.description) TextView description; - - - - public ViewHolder(View itemView) { - super(itemView); - ButterKnife.bind(this, itemView); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/WelcomeActivity.java b/app/src/main/java/com/topjohnwu/magisk/WelcomeActivity.java index 6d34d7cfd..cb3c30bde 100644 --- a/app/src/main/java/com/topjohnwu/magisk/WelcomeActivity.java +++ b/app/src/main/java/com/topjohnwu/magisk/WelcomeActivity.java @@ -4,10 +4,12 @@ import android.Manifest; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.preference.PreferenceManager; import android.support.annotation.IdRes; import android.support.annotation.NonNull; import android.support.design.widget.NavigationView; @@ -58,7 +60,15 @@ public class WelcomeActivity extends AppCompatActivity implements NavigationView } new Utils.Initialize(this).execute(); new Utils.CheckUpdates(this).execute(); - new Utils.LoadModules(getApplication(),false).execute(); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + if (!prefs.contains("oauth_key")) { + + } + if (!prefs.contains("hasCachedRepos")) { + new Utils.LoadModules(this, true).execute(); + } else { + new Utils.LoadModules(getApplication(),false).execute(); + } setSupportActionBar(toolbar); diff --git a/app/src/main/java/com/topjohnwu/magisk/module/Module.java b/app/src/main/java/com/topjohnwu/magisk/module/Module.java index c46998f08..f5ab7f1e9 100644 --- a/app/src/main/java/com/topjohnwu/magisk/module/Module.java +++ b/app/src/main/java/com/topjohnwu/magisk/module/Module.java @@ -17,59 +17,126 @@ public class Module { private String mName = null; private String mVersion = "(No version provided)"; private String mDescription = "(No description provided)"; - private String mUrl = null; + private String mUrl,mSupportUrl,mDonateUrl,mZipUrl,mBaseUrl,mManifestUrl,mAuthor; + private boolean mEnable, mRemove,mUpdateAvailable,mIsOnline; - private boolean mEnable; - private boolean mRemove; private String mId; private int mVersionCode; public Module(String path, Context context) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - Map keys = prefs.getAll(); - for(Map.Entry entry : keys.entrySet()){ - - if(entry.getValue().toString().contains(path)) { - Log.d("Magisk", "Hey, look a matching path, this guy's name is " + entry.getKey().replace("path_","")); - } - } mRemoveFile = path + "/remove"; mDisableFile = path + "/disable"; for (String line : Utils.readFile(path + "/module.prop")) { - String[] parts = line.split("=", 2); - if (parts.length != 2) { + String[] props = line.split("=", 2); + if (props.length != 2) { continue; } - String key = parts[0].trim(); + String key = props[0].trim(); if (key.charAt(0) == '#') { continue; } - String value = parts[1].trim(); - switch (key) { + String value = props[1].trim(); + switch (props[0]) { + case "versionCode": + this.mVersionCode = Integer.valueOf(props[1]); + break; case "name": - mName = value; + this.mName = value; break; - case "version": - mVersion = value; - break; - case "description": - mDescription = value; + case "author": + this.mAuthor = value; break; case "id": - mId = value; + this.mId = value; break; - case "versionCode": - try { - mVersionCode = Integer.parseInt(value); - } catch (NumberFormatException e) { - mVersionCode = 0; - } + case "version": + this.mVersion = value; + break; + case "description": + this.mDescription = value; + break; + case "donate": + this.mDonateUrl = value; + break; + case "support": + this.mSupportUrl = value; + break; + case "donateUrl": + this.mDonateUrl = value; + break; + case "zipUrl": + this.mZipUrl = value; + break; + case "baseUrl": + this.mBaseUrl = value; + break; + case "manifestUrl": + this.mManifestUrl = value; + break; + default: + Log.d("Magisk", "Module: Manifest string not recognized: " + props[0]); break; } + + + } + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + if (this.mId != null && !this.mId.isEmpty()) { + String preferenceString = "repo_" + this.mId; + String preferenceKey = prefs.getString(preferenceString,"nope"); + Log.d("Magisk", "Module: Checking for preference named " + preferenceString); + if (!preferenceKey.equals("nope")) { + Log.d("Magisk", "Module: repo_" + mId + " found."); + String entryString = prefs.getString("repo_" + mId, ""); + + String[] subStrings = entryString.split("\n"); + for (String subKeys : subStrings) { + String[] idEntry = subKeys.split("=", 2); + if (idEntry[0].equals("id")) { + if (idEntry.length != 2) { + continue; + } + + if (idEntry[1].equals(mId)) { + Log.d("Magisk", "Module: Hey, I know I'm online..."); + mIsOnline = true; + } else mIsOnline = false; + } + if (idEntry[0].equals("versionCode")) { + if (idEntry.length != 2) { + continue; + } + + if (Integer.valueOf(idEntry[1]) > mVersionCode) { + mUpdateAvailable = true; + Log.d("Magisk", "Module: Hey, I have an update..."); + } else mUpdateAvailable = false; + } + } + + + } + + SharedPreferences.Editor editor = prefs.edit(); + if (mIsOnline) { + editor.putBoolean("repo-isInstalled_" + mId, true); + + } else { + editor.putBoolean("repo-isInstalled_" + mId, false); + } + + if (mUpdateAvailable) { + editor.putBoolean("repo-canUpdate_" + mId, true); + } else { + editor.putBoolean("repo-canUpdate_" + mId, false); + } + editor.apply(); } if (mName == null) { @@ -86,11 +153,11 @@ public class Module { public Module(Repo repo) { mName = repo.getName(); - mVersion = repo.getVersion(); + mVersion = repo.getmVersion(); mDescription = repo.getDescription(); - mId = "foo"; - mVersionCode = 111; - mUrl = repo.getZipUrl(); + mId = repo.getId(); + mVersionCode = repo.getmVersionCode(); + mUrl = repo.getmZipUrl(); mEnable = true; mRemove = false; @@ -134,4 +201,8 @@ public class Module { return mRemove; } + public boolean isOnline() {return mIsOnline; } + + public boolean isUpdateAvailable() { return mUpdateAvailable; }; + } \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/module/Repo.java b/app/src/main/java/com/topjohnwu/magisk/module/Repo.java index 228e7809c..d02cedc8e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/module/Repo.java +++ b/app/src/main/java/com/topjohnwu/magisk/module/Repo.java @@ -5,124 +5,265 @@ import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.util.Log; +import com.topjohnwu.magisk.R; +import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.WebRequest; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.text.SimpleDateFormat; import java.util.Date; public class Repo { - public String name; - public String baseUrl, zipUrl, manifestUrl, logUrl, manifest, version, moduleName, moduleDescription, moduleAuthor, moduleAuthorUrl; - public Date lastUpdate; - public Boolean usesRoot, usesXposed; + private String mBaseUrl; + private String mZipUrl; + private String mLogUrl; + private String mManifestUrl; + private String mVersion; + private String mName; + private String mDescription; + private String mAuthor; + public String mAuthorUrl; + private String mId; + private String mVersionCode; + private String mSupportUrl; + private String mDonateUrl; + private String lastUpdate; private Context appContext; - private SharedPreferences prefs; + private boolean mIsInstalled,mCanUpdate; + + + public Repo(String manifestString, Context context) { + appContext = context; + ParseProps(manifestString); + + } + public Repo(String name, String url, Date updated, Context context) { appContext = context; - this.name = name; - this.baseUrl = url; - this.lastUpdate = updated; + this.mName = name; + this.mBaseUrl = url; + this.lastUpdate = updated.toString(); + this.fetch(); } public Repo(String moduleName, String moduleDescription, String zipUrl, Date lastUpdated, Context context) { - Log.d("Magisk", "Hey, I'm a repo! My name is " + moduleName); appContext = context; - this.zipUrl = zipUrl; - this.moduleDescription = moduleDescription; - this.moduleName = moduleName; - this.lastUpdate = lastUpdated; - + this.mZipUrl = zipUrl; + this.mDescription = moduleDescription; + this.mName = moduleName; + this.lastUpdate = lastUpdated.toString(); + this.fetch(); } public void fetch() { - prefs = PreferenceManager.getDefaultSharedPreferences(appContext); + WebRequest webreq = new WebRequest(); // Construct initial url for contents - Log.d("Magisk", "Manifest string is: " + baseUrl + "/contents/"); - String repoString = webreq.makeWebServiceCall(baseUrl + "/contents/", WebRequest.GET); - + Log.d("Magisk", "Repo: Fetch called, Manifest string is: " + mBaseUrl + "/contents?access_token=" + Utils.procFile(appContext.getString(R.string.some_string),appContext)); + String repoString = webreq.makeWebServiceCall(mBaseUrl + "/contents?access_token=" + Utils.procFile(appContext.getString(R.string.some_string),appContext), WebRequest.GET); try { JSONArray repoArray = new JSONArray(repoString); - for (int f = 0; f < repoArray.length(); f++) { JSONObject jsonobject = repoArray.getJSONObject(f); String name = jsonobject.getString("name"); if (name.contains(".zip")) { - this.zipUrl = jsonobject.getString("download_url"); - } else if (name.equals("module.json")) { - this.manifestUrl = jsonobject.getString("download_url"); - } else if (name.equals("Changelog.txt")) { - this.logUrl = jsonobject.getString("download_url"); + this.mZipUrl = jsonobject.getString("download_url"); + } else if (name.equals("module.prop")) { + this.mManifestUrl = jsonobject.getString("download_url"); + } else if (name.equals("changelog.txt")) { + this.mLogUrl = jsonobject.getString("download_url"); } } } catch (JSONException e) { e.printStackTrace(); } - Log.d("Magisk", "Inner fetch: " + repoString); - try { - WebRequest jsonReq = new WebRequest(); - // Construct initial url for contents - String manifestString = webreq.makeWebServiceCall(this.manifestUrl, WebRequest.GET); - JSONObject manifestObject = new JSONObject(manifestString); - Log.d("Magisk", "Object: " + manifestObject.toString()); - version = manifestObject.getString("versionCode"); - moduleName = manifestObject.getString("moduleName"); - moduleDescription = manifestObject.getString("moduleDescription"); - moduleAuthor = manifestObject.getString("moduleAuthor"); - usesRoot = Boolean.getBoolean(manifestObject.getString("usesRoot")); - usesXposed = Boolean.getBoolean(manifestObject.getString("usesXposed")); - SharedPreferences.Editor editor = prefs.edit(); - String prefsString = "[{\"moduleDescription\":\"" + moduleDescription + "\"," - + "\"moduleName\":\"" + moduleName + "\"," - + "\"moduleAuthor\":\"" + moduleAuthor + "\"," - + "\"moduleAuthorUrl\":\"" + moduleAuthorUrl + "\"," - + "\"usesRoot\":\"" + usesRoot + "\"," - + "\"usesXposed\":\"" + usesXposed + "\"," - + "\"zipUrl\":\"" + zipUrl + "\"," - + "\"lastUpdate\":\"" + lastUpdate + "\"," - + "\"logUrl\":\"" + logUrl + "\"}]"; - editor.putString("module_" + moduleName, prefsString); - editor.putBoolean("hasCachedRepos", true); - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss"); - editor.putString("updated", sdf.toString()); - Log.d("Magisk", "Storing Preferences: " + prefsString); - editor.apply(); + Log.d("Magisk", "Repo: Inner fetch: " + mManifestUrl + "?access_token=" + Utils.procFile(appContext.getString(R.string.some_string),appContext)); + WebRequest propReq = new WebRequest(); + String manifestString = propReq.makeWebServiceCall(mManifestUrl,WebRequest.GET,true); + Log.d("Magisk","Repo: parseprops called from fetch for string " + manifestString); - } catch (JSONException e) { - e.printStackTrace(); + if (ParseProps(manifestString)) { + PutProps(manifestString); } } - public String getName() { - return moduleName; + private void PutProps(String manifestString) { + manifestString = manifestString + "zipUrl=" + mZipUrl + "\nbaseUrl=" + mBaseUrl + "\nlogUrl=" + mLogUrl + "\nmanifestUrl=" + mManifestUrl; + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString("repo_" + mId, manifestString); + editor.putBoolean("hasCachedRepos", true); + editor.putString("updated_" + mId, this.lastUpdate.toString()); + Log.d("Magisk", "Storing Preferences: " + manifestString); + editor.apply(); + } + private boolean ParseProps(String string) { + + if ((string.length() <= 1) | (!string.contains("id"))) { + return false; + } else { + String lines[] = string.split("\\n"); + for (String line : lines) { + if (line != "") { + String props[] = line.split("="); + switch (props[0]) { + case "versionCode": + this.mVersionCode = props[1]; + break; + case "name": + this.mName = props[1]; + break; + case "author": + this.mAuthor = props[1]; + break; + case "id": + this.mId = props[1]; + break; + case "version": + this.mVersion = props[1]; + break; + case "description": + this.mDescription = props[1]; + break; + case "donate": + this.mDonateUrl = props[1]; + break; + case "support": + this.mSupportUrl = props[1]; + break; + case "donateUrl": + this.mDonateUrl = props[1]; + break; + case "zipUrl": + this.mZipUrl = props[1]; + break; + case "baseUrl": + this.mBaseUrl = props[1]; + break; + case "manifestUrl": + this.mManifestUrl = props[1]; + break; + case "logUrl": + this.mLogUrl = props[1]; + break; + default: + Log.d("Magisk", "Manifest string not recognized: " + props[0]); + break; + } + } + + } + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext); + if (prefs.contains("repo-isInstalled_" + this.mId)) { + mIsInstalled = prefs.getBoolean("repo-isInstalled_" + this.mId,false); + } + if (prefs.contains("repo-canUpdate_" + this.mId)) { + mCanUpdate = prefs.getBoolean("repo-canUpdate_" + this.mId,false); + } + if (prefs.contains("updated_" + this.mId)) { + lastUpdate = prefs.getString("updated_" + this.mId,""); + } + + + + return this.mId != null; + + } } - public String getVersion() { - return version; + + + + + + + + public String getStringProperty(String mValue) { + switch (mValue) { + case "author": + return mAuthor; + case "id": + return mId; + case "version": + return mVersion; + case "description": + return mDescription; + case "supportUrl": + return mSupportUrl; + case "donateUrl": + return mDonateUrl; + case "baseeUrl": + return mBaseUrl; + case "zipUrl": + return mZipUrl; + default: + return null; + } + } + + public String getName() { + return mName; + } + + public String getmVersion() { + return mVersion; + } + + public int getmVersionCode() { + return Integer.valueOf(mVersionCode); } public String getDescription() { - return moduleDescription; + return mDescription; } - public String getZipUrl() { - return zipUrl; + public String getId() { + return mId; } - public String getLogUrl() { - return logUrl; + public String getmZipUrl() { + return mZipUrl; + } + + public String getmBaseUrl() { + return mBaseUrl; + } + + public String getmLogUrl() { + return mLogUrl; } -} \ No newline at end of file + public String getmAuthor() { + return mAuthor; + } + + public String getmDonateUrl() { + return mDonateUrl; + } + + public String getmManifestUrl() { + return mManifestUrl; + } + + public String getmSupportUrl() { + return mSupportUrl; + } + + public String getLastUpdate() { + return lastUpdate; + } + + public boolean isInstalled() { return mIsInstalled; } + public boolean canUpdate() { return mCanUpdate; } +} + diff --git a/app/src/main/java/com/topjohnwu/magisk/module/RepoAdapter.java b/app/src/main/java/com/topjohnwu/magisk/module/RepoAdapter.java deleted file mode 100644 index a18d77b13..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/module/RepoAdapter.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.topjohnwu.magisk.module; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.AsyncTask; -import android.preference.PreferenceManager; -import android.util.Log; -import android.widget.Toast; - -import com.topjohnwu.magisk.utils.WebRequest; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; - -public class RepoAdapter { - private String[] result; - private static String url = "https://api.github.com/orgs/Magisk-Modules-Repo/repos"; - private static List repos = new ArrayList(); - private static final String TAG_ID = "id"; - private static final String TAG_NAME = "name"; - private Context activityContext; - private Date updatedDate, currentDate; - - public List listRepos(Context context, boolean refresh) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - if (!prefs.contains("hasCachedRepos") | refresh) { - activityContext = context; - new MyAsyncTask().execute(); - List out = null; - } else { - Log.d("Magisk", "Building from cache"); - Map map = prefs.getAll(); - repos.clear(); - for (Map.Entry entry : map.entrySet()) { - if (entry.getKey().contains("module_")) { - String repoString = entry.getValue().toString().replace(""", "\""); - JSONArray repoArray = null; - try { - repoArray = new JSONArray(repoString); - - - for (int f = 0; f < repoArray.length(); f++) { - JSONObject jsonobject = repoArray.getJSONObject(f); - String name = entry.getKey().replace("module_", ""); - name = name.replace(" ", ""); - String moduleName, moduleDescription, zipUrl; - moduleName = jsonobject.getString("moduleName"); - moduleDescription = jsonobject.getString("moduleDescription"); - zipUrl = jsonobject.getString("zipUrl"); - SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - try { - updatedDate = format.parse(jsonobject.getString("lastUpdate")); - } catch (ParseException e) { - e.printStackTrace(); - } - repos.add(new Repo(name, moduleDescription, zipUrl, updatedDate, activityContext)); - - } - } catch (JSONException e) { - e.printStackTrace(); - } - } - - } - - } - - - return repos; - } - - - class MyAsyncTask extends AsyncTask { - - @Override - protected void onPreExecute() { - super.onPreExecute(); - - } - - @Override - protected void onProgressUpdate(String... values) { - super.onProgressUpdate(values); - Toast.makeText(activityContext, "Refreshing online modules", Toast.LENGTH_SHORT).show(); - - } - - @Override - protected Void doInBackground(String... params) { - publishProgress(); - // Creating service handler class instance - WebRequest webreq = new WebRequest(); - - // Making a request to url and getting response - String jsonStr = webreq.makeWebServiceCall(url, WebRequest.GET); - Log.d("Magisk", "doInBackground Running, String: " + jsonStr + " Url: " + url); - - - try { - repos.clear(); - JSONArray jsonArray = new JSONArray(jsonStr); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject jsonobject = jsonArray.getJSONObject(i); - String name = jsonobject.getString("name"); - String url = jsonobject.getString("url"); - String lastUpdate = jsonobject.getString("updated_at"); - SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - try { - updatedDate = format.parse(lastUpdate); - - } catch (ParseException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - if (!name.contains("Repo.github.io")) { - repos.add(new Repo(name, url, updatedDate, activityContext)); - } - } - } catch (JSONException e) { - e.printStackTrace(); - } - return null; - - - } - - protected void onPostExecute(Void v) { - - - } // protected void onPostExecute(Void v) - } //class MyAsyncTask extends AsyncTask - - protected void onPreExecute() { - - } - - -} diff --git a/app/src/main/java/com/topjohnwu/magisk/module/RepoHelper.java b/app/src/main/java/com/topjohnwu/magisk/module/RepoHelper.java new file mode 100644 index 000000000..1a04c0363 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/module/RepoHelper.java @@ -0,0 +1,204 @@ +package com.topjohnwu.magisk.module; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.preference.PreferenceManager; +import android.util.Log; +import android.widget.Toast; + +import com.topjohnwu.magisk.R; +import com.topjohnwu.magisk.utils.Utils; +import com.topjohnwu.magisk.utils.WebRequest; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class RepoHelper { + private String[] result; + private static String url = "https://api.github.com/orgs/Magisk-Modules-Repo/repos?access_token="; + private static List repos = new ArrayList(); + private static final String TAG_ID = "id"; + private static final String TAG_NAME = "name"; + private static String TAG = "Magisk"; + private String mName, mId, mUrl; + private Context activityContext; + private Date updatedDate, currentDate; + private SharedPreferences prefs; + private boolean apiFail; + + public List listRepos(Context context, boolean refresh) { + prefs = PreferenceManager.getDefaultSharedPreferences(context); + activityContext = context; + if (!prefs.contains("hasCachedRepos") | refresh) { + Log.d(TAG, "RepoHelper: Building from web"); + new MyAsyncTask().execute(); + List out = null; + } else { + Log.d(TAG, "RepoHelper: Building from cache"); + BuildFromCache(); + + + } + + + return repos; + } + + private void BuildFromCache() { + repos.clear(); + Map map = prefs.getAll(); + for (Map.Entry entry : map.entrySet()) { + if (entry.getKey().contains("repo_")) { + Log.d("Magisk", "RepoHelper: found entry for repo " + entry.getKey()); + String repoString = entry.getValue().toString().replace(""", "\""); + String[] repoStrings = repoString.split("\n"); + for (String string : repoStrings) { + String[] splitStrings = string.split("="); + switch (splitStrings[0]) { + case ("id"): + mId = splitStrings[1]; + break; + case ("baseUrl"): + mUrl = splitStrings[1]; + break; + default: + break; + } + + } + Log.d("Magisk", "RepoHelper: adding repo with id of " + mId); + repos.add(new Repo(repoString, activityContext)); + + + } + + } + } + + + class MyAsyncTask extends AsyncTask { + + @Override + protected void onPreExecute() { + super.onPreExecute(); + + } + + @Override + protected void onProgressUpdate(String... values) { + super.onProgressUpdate(values); + Toast.makeText(activityContext, "Refreshing online modules", Toast.LENGTH_SHORT).show(); + + } + + @Override + protected Void doInBackground(String... params) { + publishProgress(); + // Creating service handler class instance + WebRequest webreq = new WebRequest(); + + // Making a request to url and getting response + String token = activityContext.getString(R.string.some_string); + String jsonStr = webreq.makeWebServiceCall(url + Utils.procFile(token, activityContext), WebRequest.GET); + Log.d("Magisk", "doInBackground Running, String: " + jsonStr + " Url: " + url); + if (jsonStr != null && !jsonStr.isEmpty()) { + + try { + repos.clear(); + JSONArray jsonArray = new JSONArray(jsonStr); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject jsonobject = jsonArray.getJSONObject(i); + String name = jsonobject.getString("name"); + String url = jsonobject.getString("url"); + String lastUpdate = jsonobject.getString("updated_at"); + String mId = ""; + String cacheUpdate = ""; + String manifestString = ""; + boolean doUpdate = true; + boolean hasCachedDate = false; + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + Map map = prefs.getAll(); + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().toString().contains(url)) { + Log.d("Magisk", "RepoHelper: found matching URL"); + manifestString = entry.getValue().toString(); + String[] entryStrings = entry.getValue().toString().split("\n"); + for (String valueString : entryStrings) { + String[] valueSub = valueString.split("="); + if (valueSub[0].equals("id")) { + mId = valueSub[1]; + Log.d("Magisk", "RepoHelper: Got id for package of " + mId); + if (prefs.contains("updated_" + mId)) { + cacheUpdate = prefs.getString("updated_" + mId, ""); + hasCachedDate = true; + } + } + } + + } + } + try { + updatedDate = format.parse(lastUpdate); + Log.d("Magisk", "RepoHelper: Dates found, online is " + updatedDate + " and cached is " + cacheUpdate); + + if (hasCachedDate) { + + doUpdate = !cacheUpdate.equals(updatedDate.toString()); + Log.d("Magisk", "RepoHelper: DoUpdate is " + doUpdate); + } + + + } catch (ParseException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (!name.contains("Repo.github.io")) { + if (doUpdate) { + repos.add(new Repo(name, url, updatedDate, activityContext)); + Log.d(TAG, "RepoHelper: Trying to add new repo for online refresh - " + name); + } else { + repos.add(new Repo(manifestString, activityContext)); + Log.d(TAG, "RepoHelper: Trying to add new repo using manifestString of " + mId); + } + } + for (int f = 0; f < repos.size(); f++) { + repos.get(f).fetch(); + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + apiFail = false; + } else { + apiFail = true; + } + return null; + + + } + + protected void onPostExecute(Void v) { + if (apiFail) { + Toast.makeText(activityContext, "GitHub API Limit reached, please try refreshing again in an hour.", Toast.LENGTH_LONG).show(); + } else { + BuildFromCache(); + } + + } // protected void onPostExecute(Void v) + } //class MyAsyncTask extends AsyncTask + + protected void onPreExecute() { + + } + + +} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/AnimationHelper.java b/app/src/main/java/com/topjohnwu/magisk/utils/AnimationHelper.java new file mode 100644 index 000000000..ebb1d3301 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/AnimationHelper.java @@ -0,0 +1,58 @@ +package com.topjohnwu.magisk.utils; + +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.Transformation; +import android.widget.LinearLayout; + +public class AnimationHelper { + public static void expand(final View v) { + v.measure(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + final int targetHeight = v.getMeasuredHeight(); + + v.getLayoutParams().height = 0; + v.setVisibility(View.VISIBLE); + Animation a = new Animation() { + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + v.getLayoutParams().height = interpolatedTime == 1 + ? LinearLayout.LayoutParams.WRAP_CONTENT + : (int) (targetHeight * interpolatedTime); + v.requestLayout(); + } + + @Override + public boolean willChangeBounds() { + return true; + } + }; + + // 1dp/ms + a.setDuration((int) (targetHeight / v.getContext().getResources().getDisplayMetrics().density)); + v.startAnimation(a); + } + + public static void collapse(final View v) { + final int initialHeight = v.getMeasuredHeight(); + + Animation a = new Animation() { + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + if (interpolatedTime == 1) { + v.setVisibility(View.GONE); + } else { + v.getLayoutParams().height = initialHeight - (int) (initialHeight * interpolatedTime); + v.requestLayout(); + } + } + + @Override + public boolean willChangeBounds() { + return true; + } + }; + // 1dp/ms + a.setDuration((int) (initialHeight / v.getContext().getResources().getDisplayMetrics().density)); + v.startAnimation(a); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/GitAgent.java b/app/src/main/java/com/topjohnwu/magisk/utils/GitAgent.java new file mode 100644 index 000000000..927c4cfb1 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/GitAgent.java @@ -0,0 +1,8 @@ +package com.topjohnwu.magisk.utils; + + +public class GitAgent { + + + +} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java index ebe8a68aa..40340bf3d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java @@ -10,6 +10,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; @@ -18,6 +19,7 @@ import android.preference.PreferenceManager; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AlertDialog; +import android.util.Base64; import android.util.Log; import android.view.View; import android.widget.Toast; @@ -25,7 +27,7 @@ import android.widget.Toast; import com.topjohnwu.magisk.ModulesFragment; import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.module.Module; -import com.topjohnwu.magisk.module.RepoAdapter; +import com.topjohnwu.magisk.module.RepoHelper; import com.topjohnwu.magisk.module.Repo; import org.json.JSONArray; @@ -36,17 +38,28 @@ import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; -import java.text.ParseException; -import java.text.SimpleDateFormat; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; import java.util.List; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; + public class Utils { public static int magiskVersion, remoteMagiskVersion = -1, remoteAppVersion = -1; public static String magiskLink, magiskChangelog, appChangelog, appLink, phhLink, supersuLink; private Context appContext; + private static final String TAG = "Magisk"; public static final String MAGISK_PATH = "/magisk"; public static final String MAGISK_CACHE_PATH = "/cache/magisk"; @@ -372,17 +385,49 @@ public class Utils { } } + public static String procFile(String value, Context context) { + + String cryptoPass = context.getResources().getString(R.string.pass); + try { + DESKeySpec keySpec = new DESKeySpec(cryptoPass.getBytes("UTF8")); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + SecretKey key = keyFactory.generateSecret(keySpec); + + byte[] encrypedPwdBytes = Base64.decode(value, Base64.DEFAULT); + // cipher is not thread safe + Cipher cipher = Cipher.getInstance("DES"); + cipher.init(Cipher.DECRYPT_MODE, key); + byte[] decrypedValueBytes = (cipher.doFinal(encrypedPwdBytes)); + + String decrypedValue = new String(decrypedValueBytes); + return decrypedValue; + + } catch (InvalidKeyException e) { + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (BadPaddingException e) { + e.printStackTrace(); + } catch (NoSuchPaddingException e) { + e.printStackTrace(); + } catch (IllegalBlockSizeException e) { + e.printStackTrace(); + } + return value; + } + public static class LoadModules extends AsyncTask { private Context mContext; private boolean doReload; public LoadModules(Context context, boolean reload) { - Log.d("Magisk", "LoadModules created, online is " + reload); mContext = context; doReload = reload; - - } @Override @@ -391,29 +436,26 @@ public class Utils { ModulesFragment.listModulesCache.clear(); ModulesFragment.listModulesDownload.clear(); List magisk = getModList(MAGISK_PATH); - Log.d("Magisk", "Reload called, online mode set to " + doReload); + Log.d("Magisk", "Utils: Reload called, loading modules from" + (doReload ? " the internet " : " cache")); List magiskCache = getModList(MAGISK_CACHE_PATH); - RepoAdapter mr = new RepoAdapter(); - - + RepoHelper mr = new RepoHelper(); List magiskRepos = mr.listRepos(mContext, doReload); + for (String mod : magisk) { + Log.d("Magisk","Utils: Adding module from string " + mod); ModulesFragment.listModules.add(new Module(mod,mContext)); } for (String mod : magiskCache) { + Log.d("Magisk","Utils: Adding cache module from string " + mod); ModulesFragment.listModulesCache.add(new Module(mod,mContext)); } for (Repo repo : magiskRepos) { + Log.d("Magisk","Utils: Adding repo from string " + repo.getId()); ModulesFragment.listModulesDownload.add(repo); } return null; } - - @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); - } } public static class FlashZIP extends AsyncTask { @@ -460,35 +502,6 @@ public class Utils { if (!result) { Toast.makeText(mContext, mContext.getString(R.string.manual_install, mPath), Toast.LENGTH_LONG).show(); return; - } else { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); - String jsonString = prefs.getString("module_" + mName,""); - String retSplit[] = ret.toString().split("Using path:"); - String ret2Split[] = retSplit[1].split(","); - String ret3Split[] = ret2Split[0].split("/"); - String finalSplit = "/" + ret3Split[1] + "/" + ret3Split[2]; - Log.d("Magisk","Damn, all that work for one path " + finalSplit); - if (!jsonString.equals("")) { - - JSONArray repoArray = null; - try { - repoArray = new JSONArray(jsonString); - - - for (int f = 0; f < repoArray.length(); f++) { - JSONObject jsonobject = repoArray.getJSONObject(f); - String name = mName; - Boolean installed = true; - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean("isInstalled_" + mName,true); - editor.putString("path_" + mName,finalSplit); - editor.apply(); - - } - } catch (JSONException e) { - e.printStackTrace(); - } - } } done(); } diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/WebRequest.java b/app/src/main/java/com/topjohnwu/magisk/utils/WebRequest.java index 1b782a158..d01649e97 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/WebRequest.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/WebRequest.java @@ -1,5 +1,7 @@ package com.topjohnwu.magisk.utils; +import android.util.Log; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStreamReader; @@ -18,6 +20,7 @@ public class WebRequest { static String response = null; public final static int GET = 1; public final static int POST = 2; + private boolean addNewLine; //Constructor with no parameter public WebRequest() { @@ -31,7 +34,18 @@ public class WebRequest { * @requestmethod - http request method */ public String makeWebServiceCall(String url, int requestmethod) { + addNewLine=false; + Log.d("Magisk","WebRequest: Service call received for URL " + url); return this.makeWebServiceCall(url, requestmethod, null); + + + } + + public String makeWebServiceCall(String url, int requestmethod, boolean addNewLines) { + addNewLine = addNewLines; + Log.d("Magisk","WebRequest: Service call(bool) received for URL " + url); + return this.makeWebServiceCall(url, requestmethod, null); + } /** @@ -90,7 +104,11 @@ public class WebRequest { String line; BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); while ((line = br.readLine()) != null) { - response += line; + if (addNewLine) { + response += line + "\n"; + } else { + response += line; + } } } else { response = ""; diff --git a/app/src/main/res/drawable/ic_done_black.xml b/app/src/main/res/drawable/ic_done_black.xml new file mode 100644 index 000000000..7affe9ba9 --- /dev/null +++ b/app/src/main/res/drawable/ic_done_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_system_update_alt_black.xml b/app/src/main/res/drawable/ic_system_update_alt_black.xml new file mode 100644 index 000000000..fb2d85e57 --- /dev/null +++ b/app/src/main/res/drawable/ic_system_update_alt_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/list_item_module.xml b/app/src/main/res/layout/list_item_module.xml index 6a22df864..00ce343c2 100644 --- a/app/src/main/res/layout/list_item_module.xml +++ b/app/src/main/res/layout/list_item_module.xml @@ -13,6 +13,10 @@ android:minHeight="?android:attr/listPreferredItemHeight" card_view:cardCornerRadius="2dp" card_view:cardElevation="2dp"> + + + + + + + diff --git a/app/src/main/res/layout/list_item_repo.xml b/app/src/main/res/layout/list_item_repo.xml index 353a08fc9..bda43c4fc 100644 --- a/app/src/main/res/layout/list_item_repo.xml +++ b/app/src/main/res/layout/list_item_repo.xml @@ -1,4 +1,5 @@ + + + + + + + + + + + + + + - \ No newline at end of file + + + + diff --git a/app/src/main/res/layout/list_item_repo_expanded.xml b/app/src/main/res/layout/list_item_repo_expanded.xml new file mode 100644 index 000000000..c7303199a --- /dev/null +++ b/app/src/main/res/layout/list_item_repo_expanded.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/modules_fragment.xml b/app/src/main/res/layout/modules_fragment.xml index 7c954d6a2..ae6bca833 100644 --- a/app/src/main/res/layout/modules_fragment.xml +++ b/app/src/main/res/layout/modules_fragment.xml @@ -1,54 +1,67 @@ - - - + - + - + + + + + - - + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/single_module_fragment.xml b/app/src/main/res/layout/single_module_fragment.xml index 42363f378..e6ab2c8fc 100644 --- a/app/src/main/res/layout/single_module_fragment.xml +++ b/app/src/main/res/layout/single_module_fragment.xml @@ -1,8 +1,9 @@ - @@ -24,4 +25,4 @@ android:textStyle="italic" android:visibility="gone"/> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 8eb8263ae..065a40f8a 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -2,4 +2,5 @@ 650dp 500dp + 100dp \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 983a104e4..6c927aad8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -42,10 +42,15 @@ Cache modules No modules found + An update is available + Module is up-to-date + Module is installed + Module is not installed Module will be removed at next reboot Module will not be removed at next reboot Module will be disabled at next reboot Module will be enabled at next reboot + Created by Save to SD @@ -89,4 +94,6 @@ phh\'s superuser SuperSU It seems that you have incompatible root installed\nDo you want to install Magisk compatible root now? + MagiskRox666 + GTYybRBTYf5his9kQ16ZNO7qgkBJ/5MyVe4CGceAOIoXgSnnk8FTd4F1dE9p5Eus