From 327f6f3b6a88d5fe1ad4dd84116bf008dddafe61 Mon Sep 17 00:00:00 2001 From: Igor Popov Date: Mon, 18 Sep 2017 16:10:25 +0300 Subject: [PATCH 01/36] Translated new strings; Corrected one original string. --- app/src/main/res/values-ru/strings.xml | 4 +++- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6a60ab824..6bfb94421 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -434,5 +434,7 @@ Проекты Проекты отсутствуют Карточки отсутствуют - Добавлено %s + Добавлено %s %s + Слишком много изменений. Пожалуйста, посмотрите их в браузере + Проект \ 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 12529a309..d34c995fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -566,6 +566,6 @@ No Projects No Cards Added by %s %s - Changes are large, please open in browser + Too many differences to display. Please, view them via browser Project From 3744f0bd3016f5c0b41a29445a8a2c630f9c2956 Mon Sep 17 00:00:00 2001 From: Igor Popov Date: Mon, 18 Sep 2017 20:41:05 +0300 Subject: [PATCH 02/36] Correction. --- app/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6bfb94421..51018d787 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -435,6 +435,6 @@ Проекты отсутствуют Карточки отсутствуют Добавлено %s %s - Слишком много изменений. Пожалуйста, посмотрите их в браузере + Слишком много изменений для отображения. Посмотрите их, пожалуйста, в браузере Проект \ No newline at end of file From ad2f983c45b16aca50c7d3bcdba5428393aa2660 Mon Sep 17 00:00:00 2001 From: Victor Alves Date: Tue, 19 Sep 2017 13:57:32 -0300 Subject: [PATCH 03/36] Translation Update --- app/src/main/res/values-pt-rBR/strings.xml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 25e1ed31c..d21fc1b21 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1,7 +1,7 @@ - + Carregando, por favor aguarde… Ação - Configuraçoes + Configurações Descartar Cancelar OK @@ -429,4 +429,14 @@ Ver como código Animações no App Desabilitar todas as animações no App. + Por favor envie seu pedido em inglês. + Abrir em nova janela + Projetos + Projeto + Adicionado por %s %s + Sem Projetos + Sem Cartões + Muitas diferenças para exibir. Por favor, veja-os via navegador + Não encontra suas Organizações\? + Este PR não pode ser merged agora. From 30805cf4c2f8d8b4ab0260b284ba092b3691a095 Mon Sep 17 00:00:00 2001 From: k0shk0sh Date: Sat, 23 Sep 2017 10:04:35 +0200 Subject: [PATCH 04/36] releasing 4.3.0 --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 1 + .../main/graphql/github/RepoProject.graphql | 10 ++ .../com/fastaccess/helper/PrefGetter.java | 9 ++ .../provider/scheme/SchemeParser.java | 26 ++++- .../provider/timeline/HtmlHelper.java | 19 ++-- .../timeline/handler/DrawableHandler.java | 5 +- .../timeline/handler/TableHandler.java | 4 +- .../handler/drawable/DrawableGetter.java | 6 +- .../handler/drawable/GlideDrawableTarget.java | 17 ++- .../viewholder/IssueDetailsViewHolder.java | 2 +- .../com/fastaccess/ui/base/BaseActivity.java | 66 ++++++++---- .../ui/base/BaseDialogFragment.java | 5 + .../com/fastaccess/ui/base/MainNavDrawer.kt | 2 + .../playstore/PlayStoreWarningActivity.kt | 44 ++++++++ .../ui/modules/repos/RepoPagerActivity.java | 4 +- .../projects/details/ProjectPagerActivity.kt | 11 +- .../projects/list/RepoProjectPresenter.kt | 4 +- .../ui/modules/search/SearchPresenter.java | 2 +- .../repos/files/SearchFilePresenter.java | 2 +- .../dialog/ProgressDialogFragment.java | 4 + .../pretty/helper/PrettifyHelper.java | 2 +- .../playstore_review_layout_warning.xml | 58 ++++++++++ .../layout/project_columns_layout.xml | 102 ++++++++---------- .../layout/column_card_row_layout.xml | 6 +- app/src/main/res/menu/drawer_menu.xml | 4 + app/src/main/res/raw/changelog.html | 50 +++------ app/src/main/res/values/strings.xml | 43 ++++++++ 28 files changed, 364 insertions(+), 148 deletions(-) create mode 100644 app/src/main/java/com/fastaccess/ui/modules/main/playstore/PlayStoreWarningActivity.kt create mode 100644 app/src/main/res/layouts/main_layouts/layout/playstore_review_layout_warning.xml diff --git a/app/build.gradle b/app/build.gradle index 60dc4579b..e964c6397 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,8 +29,8 @@ android { applicationId "com.fastaccess.github" minSdkVersion 21 targetSdkVersion 26 - versionCode 420 - versionName "4.2.0" + versionCode 430 + versionName "4.3.0" buildConfigString "GITHUB_CLIENT_ID", (buildProperties.secrets['github_client_id'] | buildProperties.notThere['github_client_id']).string buildConfigString "GITHUB_SECRET", (buildProperties.secrets['github_secret'] | buildProperties.notThere['github_secret']).string buildConfigString "IMGUR_CLIENT_ID", (buildProperties.secrets['imgur_client_id'] | buildProperties.notThere['imgur_client_id']).string diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 965eec58d..1867eb844 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -297,6 +297,7 @@ + intentOptional = returnNonNull(trending, userIntent, repoIssues, repoPulls, pullRequestIntent, commit, commits, - createIssueIntent, issueIntent, releasesIntent, repoIntent, repoWikiIntent, blob); + Optional intentOptional = returnNonNull(trending, projects, userIntent, repoIssues, repoPulls, + pullRequestIntent, commit, commits, createIssueIntent, issueIntent, releasesIntent, repoIntent, + repoWikiIntent, blob); Optional empty = Optional.empty(); if (intentOptional != null && intentOptional.isPresent() && intentOptional != empty) { Intent intent = intentOptional.get(); @@ -264,6 +267,25 @@ private static boolean getInvitationIntent(@NonNull Uri uri) { } } + @Nullable private static Intent getRepoProject(@NonNull Context context, @NonNull Uri uri) { + List segments = uri.getPathSegments(); + if (segments == null || segments.size() < 3) return null; + String owner = segments.get(0); + String repoName = segments.get(1); + if (segments.size() == 3 && "projects".equalsIgnoreCase(segments.get(2))) { + return RepoPagerActivity.createIntent(context, repoName, owner, RepoPagerMvp.PROJECTS); + } else if (segments.size() == 4 && "projects".equalsIgnoreCase(segments.get(2))) { + try { + int projectId = Integer.parseInt(segments.get(segments.size() - 1)); + if (projectId > 0) { + return ProjectPagerActivity.Companion.getIntent(context, owner, repoName, projectId, + LinkParserHelper.isEnterprise(uri.toString())); + } + } catch (Exception ignored) {} + } + return null; + } + @Nullable private static Intent getWiki(@NonNull Context context, @NonNull Uri uri) { List segments = uri.getPathSegments(); if (segments == null || segments.size() < 3) return null; diff --git a/app/src/main/java/com/fastaccess/provider/timeline/HtmlHelper.java b/app/src/main/java/com/fastaccess/provider/timeline/HtmlHelper.java index f963c359b..2a8df9e37 100644 --- a/app/src/main/java/com/fastaccess/provider/timeline/HtmlHelper.java +++ b/app/src/main/java/com/fastaccess/provider/timeline/HtmlHelper.java @@ -86,7 +86,7 @@ private static HtmlSpanner initHtml(@NonNull TextView textView, int width) { mySpanner.setStripExtraWhiteSpace(true); mySpanner.registerHandler("pre", new PreTagHandler(windowBackground, true, theme)); mySpanner.registerHandler("code", new PreTagHandler(windowBackground, false, theme)); - mySpanner.registerHandler("img", new DrawableHandler(textView)); + mySpanner.registerHandler("img", new DrawableHandler(textView, width)); mySpanner.registerHandler("g-emoji", new EmojiHandler()); mySpanner.registerHandler("blockquote", new QouteHandler(windowBackground)); mySpanner.registerHandler("b", new BoldHandler()); @@ -116,15 +116,14 @@ private static HtmlSpanner initHtml(@NonNull TextView textView, int width) { } @ColorInt public static int getWindowBackground(@PrefGetter.ThemeType int theme) { - switch (theme) { - case PrefGetter.AMLOD: - return Color.parseColor("#0B162A"); - case PrefGetter.BLUISH: - return Color.parseColor("#111C2C"); - case PrefGetter.DARK: - return Color.parseColor("#22252A"); - default: - return Color.parseColor("#EEEEEE"); + if (theme == PrefGetter.AMLOD) { + return Color.parseColor("#0B162A"); + } else if (theme == PrefGetter.BLUISH) { + return Color.parseColor("#111C2C"); + } else if (theme == PrefGetter.DARK) { + return Color.parseColor("#22252A"); + } else { + return Color.parseColor("#EEEEEE"); } } diff --git a/app/src/main/java/com/fastaccess/provider/timeline/handler/DrawableHandler.java b/app/src/main/java/com/fastaccess/provider/timeline/handler/DrawableHandler.java index 21fa616c7..277d3b67f 100644 --- a/app/src/main/java/com/fastaccess/provider/timeline/handler/DrawableHandler.java +++ b/app/src/main/java/com/fastaccess/provider/timeline/handler/DrawableHandler.java @@ -8,6 +8,7 @@ import com.fastaccess.provider.timeline.handler.drawable.DrawableGetter; import net.nightwhistler.htmlspanner.TagNodeHandler; +import net.nightwhistler.htmlspanner.spans.CenterSpan; import org.htmlcleaner.TagNode; @@ -22,6 +23,7 @@ @AllArgsConstructor public class DrawableHandler extends TagNodeHandler { private TextView textView; + private int width; @SuppressWarnings("ConstantConditions") private boolean isNull() { return textView == null; @@ -32,8 +34,9 @@ if (!InputHelper.isEmpty(src)) { builder.append(""); if (isNull()) return; - DrawableGetter imageGetter = new DrawableGetter(textView); + DrawableGetter imageGetter = new DrawableGetter(textView, width); builder.setSpan(new ImageSpan(imageGetter.getDrawable(src)), start, builder.length(), SPAN_EXCLUSIVE_EXCLUSIVE); + builder.setSpan(new CenterSpan(), start, builder.length(), SPAN_EXCLUSIVE_EXCLUSIVE); appendNewLine(builder); } } diff --git a/app/src/main/java/com/fastaccess/provider/timeline/handler/TableHandler.java b/app/src/main/java/com/fastaccess/provider/timeline/handler/TableHandler.java index a3d1a5371..7aac34c2a 100644 --- a/app/src/main/java/com/fastaccess/provider/timeline/handler/TableHandler.java +++ b/app/src/main/java/com/fastaccess/provider/timeline/handler/TableHandler.java @@ -85,8 +85,7 @@ public void setTypeFace(Typeface typeFace) { this.typeFace = typeFace; } - @Override - public boolean rendersContent() { + @Override public boolean rendersContent() { return true; } @@ -158,6 +157,7 @@ private int calculateRowHeight(List row) { } @Override public void handleTagNode(TagNode node, SpannableStringBuilder builder, int start, int end) { + builder.append("\n"); Table table = getTable(node); for (int i = 0; i < table.getRows().size(); i++) { List row = table.getRows().get(i); diff --git a/app/src/main/java/com/fastaccess/provider/timeline/handler/drawable/DrawableGetter.java b/app/src/main/java/com/fastaccess/provider/timeline/handler/drawable/DrawableGetter.java index 759fe787f..ed2c91881 100644 --- a/app/src/main/java/com/fastaccess/provider/timeline/handler/drawable/DrawableGetter.java +++ b/app/src/main/java/com/fastaccess/provider/timeline/handler/drawable/DrawableGetter.java @@ -21,11 +21,13 @@ public class DrawableGetter implements Html.ImageGetter, Drawable.Callback { private WeakReference container; private final Set cachedTargets; + private final int width; - public DrawableGetter(TextView tv) { + public DrawableGetter(TextView tv, int width) { tv.setTag(R.id.drawable_callback, this); this.container = new WeakReference<>(tv); this.cachedTargets = new HashSet<>(); + this.width = width; } @Override public Drawable getDrawable(@NonNull String url) { @@ -35,7 +37,7 @@ public DrawableGetter(TextView tv) { final GenericRequestBuilder load = Glide.with(context) .load(url) .dontAnimate(); - final GlideDrawableTarget target = new GlideDrawableTarget(urlDrawable, container); + final GlideDrawableTarget target = new GlideDrawableTarget(urlDrawable, container, width); load.into(target); cachedTargets.add(target); } diff --git a/app/src/main/java/com/fastaccess/provider/timeline/handler/drawable/GlideDrawableTarget.java b/app/src/main/java/com/fastaccess/provider/timeline/handler/drawable/GlideDrawableTarget.java index 35ff6c95c..12c6cd673 100644 --- a/app/src/main/java/com/fastaccess/provider/timeline/handler/drawable/GlideDrawableTarget.java +++ b/app/src/main/java/com/fastaccess/provider/timeline/handler/drawable/GlideDrawableTarget.java @@ -15,17 +15,28 @@ class GlideDrawableTarget extends SimpleTarget { private final UrlDrawable urlDrawable; private final WeakReference container; + private final int width; - GlideDrawableTarget(UrlDrawable urlDrawable, WeakReference container) { + GlideDrawableTarget(UrlDrawable urlDrawable, WeakReference container, int width) { this.urlDrawable = urlDrawable; this.container = container; + this.width = width; } @Override public void onResourceReady(GlideDrawable resource, GlideAnimation glideAnimation) { if (container != null && container.get() != null) { TextView textView = container.get(); - float width = (float) (resource.getIntrinsicWidth() / 1.3); - float height = (float) (resource.getIntrinsicHeight() / 1.3); + float width; + float height; + if (resource.getIntrinsicWidth() >= this.width) { + float downScale = (float) resource.getIntrinsicWidth() / this.width; + width = (float) (resource.getIntrinsicWidth() / downScale / 1.3); + height = (float) (resource.getIntrinsicHeight() / downScale / 1.3); + } else { + float multiplier = (float) this.width / resource.getIntrinsicWidth(); + width = (float) resource.getIntrinsicWidth() * multiplier; + height = (float) resource.getIntrinsicHeight() * multiplier; + } Rect rect = new Rect(0, 0, Math.round(width), Math.round(height)); resource.setBounds(rect); urlDrawable.setBounds(rect); diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueDetailsViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueDetailsViewHolder.java index 628e854ea..8e51f713a 100644 --- a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueDetailsViewHolder.java +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueDetailsViewHolder.java @@ -193,7 +193,7 @@ private void setup(User user, String description, ReactionsModel reactionsModel) if (reactionsModel != null) { appendEmojies(reactionsModel); } - if (description != null && !description.trim().isEmpty()) { + if (!InputHelper.isEmpty(description)) { HtmlHelper.htmlIntoTextView(comment, description, viewGroup.getWidth()); } else { comment.setText(R.string.no_description_provided); diff --git a/app/src/main/java/com/fastaccess/ui/base/BaseActivity.java b/app/src/main/java/com/fastaccess/ui/base/BaseActivity.java index 37c6072a5..a1ee39225 100644 --- a/app/src/main/java/com/fastaccess/ui/base/BaseActivity.java +++ b/app/src/main/java/com/fastaccess/ui/base/BaseActivity.java @@ -44,6 +44,7 @@ import com.fastaccess.ui.modules.login.chooser.LoginChooserActivity; import com.fastaccess.ui.modules.main.MainActivity; import com.fastaccess.ui.modules.main.orgs.OrgListDialogFragment; +import com.fastaccess.ui.modules.main.playstore.PlayStoreWarningActivity; import com.fastaccess.ui.modules.repos.code.commit.details.CommitPagerActivity; import com.fastaccess.ui.modules.repos.issues.issue.details.IssuePagerActivity; import com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.PullRequestPagerActivity; @@ -107,29 +108,11 @@ public abstract class BaseActivity, private val extraNav: Navigati item.itemId == R.id.notifications -> view.startActivity(Intent(view, NotificationActivity::class.java)) item.itemId == R.id.trending -> view.startActivity(Intent(view, TrendingActivity::class.java)) item.itemId == R.id.reportBug -> view.startActivity(CreateIssueActivity.startForResult(view)) + item.itemId == R.id.faq -> view.startActivity(Intent(view, PlayStoreWarningActivity::class.java)) } } }, 250) diff --git a/app/src/main/java/com/fastaccess/ui/modules/main/playstore/PlayStoreWarningActivity.kt b/app/src/main/java/com/fastaccess/ui/modules/main/playstore/PlayStoreWarningActivity.kt new file mode 100644 index 000000000..0de394a12 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/main/playstore/PlayStoreWarningActivity.kt @@ -0,0 +1,44 @@ +package com.fastaccess.ui.modules.main.playstore + +import android.os.Build +import android.os.Bundle +import android.text.Html +import android.widget.TextView +import butterknife.OnClick +import com.fastaccess.R +import com.fastaccess.helper.PrefGetter +import com.fastaccess.ui.base.BaseActivity +import com.fastaccess.ui.base.mvp.BaseMvp +import com.fastaccess.ui.base.mvp.presenter.BasePresenter + +/** + * Created by Hashemsergani on 21.09.17. + */ +class PlayStoreWarningActivity : BaseActivity>() { + + @OnClick(R.id.done) fun onDone() { + PrefGetter.setPlayStoreWarningShowed() + finish() + } + + override fun layout(): Int = R.layout.playstore_review_layout_warning + + override fun isTransparent(): Boolean = true + + override fun canBack(): Boolean = false + + override fun isSecured(): Boolean = true + + override fun providePresenter(): BasePresenter = BasePresenter() + + override fun onBackPressed() {} + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + findViewById(R.id.description).text = Html.fromHtml(getString(R.string.fasthub_faq_description), Html.FROM_HTML_MODE_LEGACY) + } else { + findViewById(R.id.description).text = Html.fromHtml(getString(R.string.fasthub_faq_description)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerActivity.java b/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerActivity.java index 1125db193..a233f4366 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerActivity.java +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerActivity.java @@ -341,7 +341,9 @@ public static Intent createIntent(@NonNull Context context, @NonNull String repo } this.navType = navType; //noinspection WrongConstant - if (bottomNavigation.getSelectedIndex() != navType) bottomNavigation.setSelectedIndex(navType, true); + try { + if (bottomNavigation.getSelectedIndex() != navType) bottomNavigation.setSelectedIndex(navType, true); + } catch (Exception ignored) {} showHideFab(); getPresenter().onModuleChanged(getSupportFragmentManager(), navType); } diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/projects/details/ProjectPagerActivity.kt b/app/src/main/java/com/fastaccess/ui/modules/repos/projects/details/ProjectPagerActivity.kt index 6e5374688..2e4b32acb 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/projects/details/ProjectPagerActivity.kt +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/projects/details/ProjectPagerActivity.kt @@ -98,10 +98,12 @@ class ProjectPagerActivity : BaseActivity(), RepoProjectMv override fun onItemClick(position: Int, v: View, item: RepoProjectsOpenQuery.Node) { item.databaseId()?.let { - ProjectPagerActivity.startActivity(v.context, login, repoId, it.toLong()) + ProjectPagerActivity.startActivity(v.context, login, repoId, it.toLong(), isEnterprise) } } @@ -77,6 +77,7 @@ class RepoProjectPresenter : BasePresenter(), RepoProjectMv val list = arrayListOf() it.data()?.repository()?.let { it.projects().let { + lastPage = if (it.pageInfo().hasNextPage()) Int.MAX_VALUE else 0 pages.clear() count = it.totalCount() it.edges()?.let { @@ -106,6 +107,7 @@ class RepoProjectPresenter : BasePresenter(), RepoProjectMv val list = arrayListOf() it.data()?.repository()?.let { it.projects().let { + lastPage = if (it.pageInfo().hasNextPage()) Int.MAX_VALUE else 0 pages.clear() count = it.totalCount() it.edges()?.let { diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/SearchPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/search/SearchPresenter.java index 8ced1af4c..870215ba4 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/search/SearchPresenter.java +++ b/app/src/main/java/com/fastaccess/ui/modules/search/SearchPresenter.java @@ -41,7 +41,7 @@ class SearchPresenter extends BasePresenter implements SearchMvp } @Override public void onSearchClicked(@NonNull ViewPager viewPager, @NonNull AutoCompleteTextView editText) { - boolean isEmpty = InputHelper.isEmpty(editText) || InputHelper.toString(editText).length() < 3; + boolean isEmpty = InputHelper.isEmpty(editText) || InputHelper.toString(editText).length() < 2; editText.setError(isEmpty ? editText.getResources().getString(R.string.minimum_three_chars) : null); if (!isEmpty) { editText.dismissDropDown(); diff --git a/app/src/main/java/com/fastaccess/ui/modules/search/repos/files/SearchFilePresenter.java b/app/src/main/java/com/fastaccess/ui/modules/search/repos/files/SearchFilePresenter.java index 8c88c8e82..fd974684f 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/search/repos/files/SearchFilePresenter.java +++ b/app/src/main/java/com/fastaccess/ui/modules/search/repos/files/SearchFilePresenter.java @@ -19,7 +19,7 @@ public class SearchFilePresenter extends BasePresenter imple } @Override public void onSearchClicked(@NonNull FontEditText editText, boolean inPath) { - boolean isEmpty = InputHelper.isEmpty(editText) || InputHelper.toString(editText).length() < 3; + boolean isEmpty = InputHelper.isEmpty(editText) || InputHelper.toString(editText).length() < 2; editText.setError(isEmpty ? editText.getResources().getString(R.string.minimum_three_chars) : null); if (!isEmpty) { AppHelper.hideKeyboard(editText); diff --git a/app/src/main/java/com/fastaccess/ui/widgets/dialog/ProgressDialogFragment.java b/app/src/main/java/com/fastaccess/ui/widgets/dialog/ProgressDialogFragment.java index 19048f83c..cdd8db6fb 100644 --- a/app/src/main/java/com/fastaccess/ui/widgets/dialog/ProgressDialogFragment.java +++ b/app/src/main/java/com/fastaccess/ui/widgets/dialog/ProgressDialogFragment.java @@ -23,6 +23,10 @@ public class ProgressDialogFragment extends BaseDialogFragment { + public ProgressDialogFragment() { + suppressAnimation = true; + } + public static final String TAG = ProgressDialogFragment.class.getSimpleName(); @NonNull public static ProgressDialogFragment newInstance(@NonNull Resources resources, @StringRes int msgId, boolean isCancelable) { diff --git a/app/src/main/java/com/prettifier/pretty/helper/PrettifyHelper.java b/app/src/main/java/com/prettifier/pretty/helper/PrettifyHelper.java index 03fd73961..7f3087f31 100644 --- a/app/src/main/java/com/prettifier/pretty/helper/PrettifyHelper.java +++ b/app/src/main/java/com/prettifier/pretty/helper/PrettifyHelper.java @@ -8,7 +8,7 @@ public class PrettifyHelper { - @NonNull private static String getHtmlContent(@NonNull String css, @NonNull String text, @NonNull boolean wrap, boolean isDark) { + @NonNull private static String getHtmlContent(@NonNull String css, @NonNull String text, boolean wrap, boolean isDark) { return "\n" + "\n" + "\n" + diff --git a/app/src/main/res/layouts/main_layouts/layout/playstore_review_layout_warning.xml b/app/src/main/res/layouts/main_layouts/layout/playstore_review_layout_warning.xml new file mode 100644 index 000000000..9b1049010 --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/playstore_review_layout_warning.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/project_columns_layout.xml b/app/src/main/res/layouts/main_layouts/layout/project_columns_layout.xml index 22c30db4a..24bb34727 100644 --- a/app/src/main/res/layouts/main_layouts/layout/project_columns_layout.xml +++ b/app/src/main/res/layouts/main_layouts/layout/project_columns_layout.xml @@ -4,7 +4,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="?colorPrimary"> - - + android:paddingTop="@dimen/spacing_normal" + tools:text="One must need the visitor in order to study the lord of great awareness."/> - + - + android:background="?selectableItemBackgroundBorderless" + android:padding="@dimen/spacing_micro" + android:src="@drawable/ic_edit"/> - - - + - + - - - + diff --git a/app/src/main/res/layouts/row_layouts/layout/column_card_row_layout.xml b/app/src/main/res/layouts/row_layouts/layout/column_card_row_layout.xml index 49d5b3de8..f1cb6e86e 100644 --- a/app/src/main/res/layouts/row_layouts/layout/column_card_row_layout.xml +++ b/app/src/main/res/layouts/row_layouts/layout/column_card_row_layout.xml @@ -5,9 +5,13 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="@dimen/spacing_micro" + android:layout_marginBottom="@dimen/spacing_micro" + android:layout_marginEnd="@dimen/grid_spacing" + android:layout_marginStart="@dimen/grid_spacing" + android:layout_marginTop="@dimen/grid_spacing" android:foreground="?android:selectableItemBackground" app:cardBackgroundColor="?card_background" + app:cardCornerRadius="@dimen/grid_spacing" app:contentPaddingBottom="@dimen/spacing_normal" app:contentPaddingLeft="@dimen/spacing_xs_large" app:contentPaddingTop="@dimen/spacing_normal"> diff --git a/app/src/main/res/menu/drawer_menu.xml b/app/src/main/res/menu/drawer_menu.xml index 6aa8c7b45..4608e68ab 100644 --- a/app/src/main/res/menu/drawer_menu.xml +++ b/app/src/main/res/menu/drawer_menu.xml @@ -55,6 +55,10 @@ android:id="@+id/reportBug" android:icon="@drawable/ic_bug" android:title="@string/report_issue"/> +

FastHub changelog

-

Version 4.2.0 (Create, Edit & Delete files (make Commits)) +

Version 4.3.0 (Project Columns and Cards)

Reporting Issues or Feature Requests in Google Play review section, will be ignored or might even get your account to be blocked from FastHub. You are using an app for GitHub, which provides a proper way to report issues.
- Please report the issues in FastHub repo instead, by opening the Drawer Menu, click about and click “Report an Issue”PLEASE - USE IT. + Please report the issues in FastHub repo instead, by opening the Drawer Menu and clicking on “Report an Issue” + PLEASE USE IT.

-

Bugs , Enhancements & new Features (3.2.0) +

Bugs , Enhancements & new Features (4.3.0)

    -
  • (New) Make commits on repos from FastHub (this only applies for repo owners so far). - PRO -
  • -
  • (New) Long pressing files/directories will open up their Commit/Git History.
  • -
  • (New) Indicates PR review line has a comment.
  • -
  • (New) Showing number of addition, deletion & files in PR files tab.
  • -
  • (New) Untoggle MD rendering to see their actual code.
  • -
  • (New) Settings option to disable Animations in FastHub.
  • -
  • (New) Copy SHA from Commit.
  • -
  • (New) Forks, Watchers & Stargazers links handling.
  • -
  • (Enhancement) Loading larger number of comments per page.
  • -
  • (Enhancement) Hide fab while scrolling in Issues & PR tab.
  • -
  • (Fix) PR Status where it got somehow broken in previous release.
  • -
  • (Fix) A text under PR status will indicates if the PR is mergable or not.
  • -
  • (Fix) Tagging users in full screen editor.
  • -
  • (Fix) Comments text selection where sometime they aren’t selectable.
  • -
  • (Fix) PR Review footer is invisible in (4.1.0).
  • -
  • (Fix) Cursor position after inserting emoji, links & images.
  • -
  • (Fix) Teal customized accent was displayed as green.
  • -
  • (Fix) Some crashes from the crash report.
  • +
  • Project Columns & Cards (Edit, Create & Delete)
  • +
  • (New) Repo Collaborators now can Edit, delete & create files.
  • +
  • (New) Repo Collaborators now can Edit, delete & Comments.
  • +
  • (New) Long press in Feeds to navigate directly to Repo.
  • +
  • (Enhancement) Made Search to have minimum 2 chars.
  • +
  • (Enhancement) Adding blockable progress when adding new comment.
  • +
  • (Fix) Crash in PRs & Issues comments due to Table rendering!.
  • +
  • (Fix) ReadMe scrolling when Device Animation is turned off.
  • +
  • (Fix) Closed/Opened Issue pagination where most of the issues in the page are PRs (GitHub API!!!!).
  • (Fix) Lots of bug fixes.
  • There are more stuff are not mentioned, find them out :stuck_out_tongue:
@@ -48,18 +37,7 @@

What left in FastHub?

- So far, FastHub has implemented almost all the features of GitHub, besides forProject Cards. Hopefully in the - next release FastHub will include that. The following releases will mainly be bug fixes or if a major feature is implemented. - -

-
-

How old is FastHub now? -

-
-

- FastHub is now 5 months & a week old. Since v1.0.0 it has really grow with each and every release. The only way this was - possible, was due to the community helping out either via reporting bugs, feature requests or even give hand to fix things or - implement things in the app. I’m really grateful for having such a great community. Thank you guys! + So far, FastHub has implemented all the features of GitHub, I'll continue to fix bugs, implement feature requests if any!.

diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 12529a309..60693813d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -568,4 +568,47 @@ Added by %s %s Changes are large, please open in browser Project + PLEASE READ! + + • Why can\'t I see my Organizations either Private or Public ones? +

Open up https://github.com/settings/applications and look for FastHub, open it then scroll to Organization access and click on Grant Button, + alternatively login via Access Token which will ease this setup.

+ +
• I tried to login via Access Token & OTP but it does not work?
+

You can\'t login via Access Token & OTP all together due to the lifetime of the OTP code, you\'ll be required to login in every few seconds.

+ +
• Why my Private Repo & Enterprise Wiki does not show up?
+

It\'s due to FastHub scraping GitHub Wiki page & Private Repos require session token that FastHub doesn\'t have.

+ +
• I login with Enterprise account but can\'t interact with anything other than my Enterprise GitHub?
\n +

Well, logically, you can\'t access anything else other than your Enterprise, but FastHub made that possible but can\'t do much about it, + in most cases since your login credential doesn\'t exists in GitHub server. But in few + cases your GitHub account Oauth token will do the trick.

+ +
• I\'m having problems editing Issues/PRs?
+

If you are editing a public Org repo, then please contact your Org to grant access to FastHub or use Access Token to login!.

+ +
• I\'m blocked from using FastHub, WHY?
+

Simply, because you did rate FastHub low because you wanted this and that and never updated your review, well, + why would you keep using it anyway?

+ +
• I\'m having this issue or I want this & that!!
+

Head to https://github.com/k0shk0sh/FastHub/issues/new and create new issue for bugs or feature requests, I really do encourage you to + search before posting something that is duplicated as that will result to it being closed immediately.

+ + Message from the developer of FastHub,\n + +

Hello Dear Developers,

\n +

+ As you might know FastHub is an open source App & has grow so fast and that\'s because of the help from the community and you + guys.\nReviewing FastHub low in the PlayStore because you want to report an issue or request any feature will result to it being + ignored.\nAs developers I think we understand how much effort does it take to make an App like FastHub.\nSome don\'t get that at all + and instead of reporting issues/features in FastHub\'s repo they rate FastHub low in the PlayStore thinking that this will force + the developer (ME) to implement and fix their stuff so they update their review, but this doesn\'t happen, instead they either never + update their review or they\'ll request something else (so typical).\nPlease do report Issues & Features in FastHub repo, open + up the drawer menu & click on the Report Issue & it\'ll be posted directly to FastHub repo whereI can help/assist you. +

+ ]]>
+ FAQ
From 268f7fc35ef658a3c98757e6ef171103edb12dbc Mon Sep 17 00:00:00 2001 From: k0shk0sh Date: Sat, 23 Sep 2017 10:14:37 +0200 Subject: [PATCH 05/36] made changelog dialog to not be cancelable --- .../ui/modules/changelog/ChangelogBottomSheetDialog.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/com/fastaccess/ui/modules/changelog/ChangelogBottomSheetDialog.java b/app/src/main/java/com/fastaccess/ui/modules/changelog/ChangelogBottomSheetDialog.java index c88089a79..c3f8acae5 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/changelog/ChangelogBottomSheetDialog.java +++ b/app/src/main/java/com/fastaccess/ui/modules/changelog/ChangelogBottomSheetDialog.java @@ -1,5 +1,6 @@ package com.fastaccess.ui.modules.changelog; +import android.app.Dialog; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -61,6 +62,13 @@ public class ChangelogBottomSheetDialog extends BaseMvpBottomSheetDialogFragment return new ChangelogPresenter(); } + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.setCancelable(false); + dialog.setCanceledOnTouchOutside(false); + return dialog; + } + private void showChangelog(String html) { if (prettifyWebView == null) return; webProgress.setVisibility(View.GONE); From f34bea2dfe20b7866fb0a7dd8cfe50a49b260e19 Mon Sep 17 00:00:00 2001 From: Yakov Date: Sun, 24 Sep 2017 02:58:11 -0400 Subject: [PATCH 06/36] Fixes #997 --- app/src/main/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e21b0e286..f750da9ad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -606,8 +606,8 @@ ignored.\nAs developers I think we understand how much effort does it take to make an App like FastHub.\nSome don\'t get that at all and instead of reporting issues/features in FastHub\'s repo they rate FastHub low in the PlayStore thinking that this will force the developer (ME) to implement and fix their stuff so they update their review, but this doesn\'t happen, instead they either never - update their review or they\'ll request something else (so typical).\nPlease do report Issues & Features in FastHub repo, open - up the drawer menu & click on the Report Issue & it\'ll be posted directly to FastHub repo whereI can help/assist you. + update their review or they\'ll request something else (so typical).\nPlease do report Issues & Features in the FastHub repo, open + up the drawer menu & click on the Report Issue & it\'ll be posted directly to FastHub repo where I can help/assist you.

]]> FAQ From 96f5a2d4826f2fffd4c14bc17f95103ce6a34a37 Mon Sep 17 00:00:00 2001 From: maple3142 Date: Sun, 24 Sep 2017 16:18:35 +0800 Subject: [PATCH 07/36] update translations --- app/src/main/res/values-zh-rTW/strings.xml | 53 ++++++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a301817cd..9ab4b395f 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -426,14 +426,14 @@ APP 選擇通知提示聲 停用 GIF 自動播放 停用 GIF 自動播放 - 請用英文來提交你的請求 - 請登入到GitHub帳號以使用所有的api功能。否則透過企業帳號向GitHub api發送token將會被拒絕。 + 請用英文來提交你的請求 + 請登入到 GitHub 帳號以使用所有的 api 功能。否則透過企業帳號向 GitHub api 發送 token 將會被拒絕。 不匹配 原發布者 取消 Reviews 請求更改 Google Play 服務不可用 - 編輯Gist + 編輯 Gist 內容 展開 複製 SHA-1 @@ -441,9 +441,54 @@ APP 停用 App 內部動畫 在每個地方停用 App 內部動畫 這個 PR 目前沒辦法被 merge - " 專案" + 專案 沒有專案 沒有卡 由 %s 新增 找不到您的組織嗎? + 在新視窗中開啟 + 差異太多無法顯示。請於瀏覽器檢視它們 + 專案 + FAQ + + • 為何我無法看到我的 組織私人 / 公開 的東西? +

打開 https://github.com/settings/applications 並找到 FastHub, 點擊它並滑到 Organization access 並點擊 Grant 按鈕, + 或是透過 Access Token 登入將不需要做這件事

+ +
• 我試著透過 Access Token & OTP 登入,但沒作用?
+

你不能透過 Access Token & OTP 登入,因為 OTP code 的時限緣故, 你會需要在幾秒鐘內登入。

+ +
• 為何我的私人&企業的 Repo Wiki 無法顯示?
+

因為 FastHub 在擷取 GitHub Wiki & 私人 Repos 時需要 session token ,但 FastHub 沒有。

+ +
• 使用企業帳號時無法與任何其他非我的企業的東西互動?
\n +

邏輯上來說,你無法存取任何東西非你的企業的東西,不過 FastHub 讓它能成功存取,但沒辦法再做到更多了。 + 在大部分的情況下你的登入認證不存在於 GitHub 的伺服器上。 + 但在少數情形之下你的 GitHub 帳號的 Oauth token 能達成這個技巧。

+ +
• 我在編輯 Issues/PRs 遇到困難?
+

如果你正在編輯一個公開組職的 Repo ,請聯絡你的組織以取得權限給 FastHub 或是使用 Access Token 來登入!

+ +
• 我被禁止使用 FastHub 了,為什麼?
+

很簡單,因為想要某些功能而給了 FastHub 低評價卻從來不更新你的評價。 + 所以為何你要繼續使用呢?

+ +
• 我有困難/我想要有新功能!!
+

前往 https://github.com/k0shk0sh/FastHub/issues/new 並創建一個 bug/feature requests 的 issue。 + 我非常鼓勵你在發布某些東西前先搜尋有沒有重複的,否則會被馬上關閉。

+ + 給 FastHub 開發者們的訊息\n + +

開發者們你好,

\n +

+ 你應該知道 FastHub 是一個成長很快開源的 App & 是因為許多來自社群的幫助以及你們。 + \n因為想要回報 issue 或是 request 而在 Play商店 給 FastHub 低評價將會被忽略。 + \n作為一個開發者我想我們應該知道開發一個像是 FastHub 要付出多大的努力。 + \n有些人不知道要在 FastHub 的 Repo 回報而是在 Play商店 給低評價並且認為這會強制開發者(我) 去實作及修復他們想要的東西,而他們會更新評價。 + \n但是這並沒有發生過,他們不是不修改評價就是要求其他功能(如此典型)。 + \n請於 FastHub repo 回報 Issue & Features,打開側欄功能表並點擊"回報 Issue",這將會被直接發布在 FastHub repo (我能夠幫助和協助你的地方)。 +

+ ]]>
+ 請閱讀! From 26857206cc7816af001104524850c26dfca26551 Mon Sep 17 00:00:00 2001 From: andylizi Date: Sun, 24 Sep 2017 18:34:24 +0800 Subject: [PATCH 08/36] Update Simplified-Chinese translation Lots of correction and improvement. Translate some newly added strings. --- app/src/main/res/values-zh-rCN/strings.xml | 437 ++++++++++++--------- 1 file changed, 254 insertions(+), 183 deletions(-) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c02878d0e..fe2491dea 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1,13 +1,33 @@ - + + + - 加载中, 请稍候… + 加载中, 请稍候... 动作 设置 + 丢弃 取消 确定 无可用数据 搜索 - 请登录以继续使用FastHub + 请登录以使用 FastHub 登录你的 GitHub 账户来使用 FastHub 无法登录 登录 @@ -26,30 +46,40 @@ 取消关注 用户 详细信息 - 检测到存档文件,请下载文件查看其内容。 - 最少字数(3) + 检测到存档文件, 请下载以查看内容. + 最少字数 (3) 未找到文件 - 没有简介 - 下载中… - 下载文件中… + 没有 Readme + 下载中... + 下载文件中... 发布于 + 草稿 发布 没有内容 贡献者 + 贡献 - 关闭缺陷跟踪 - 重新打开缺陷跟踪 + 请使用英语描述您的问题. + 关闭 Issue + 重新打开 Issue 重新打开 关闭 已重新打开 - 锁定意味着:\n·其他用户无法为此缺陷跟踪添加新评论。\n·您和其他有权访问此仓库的协作者仍然可以留下所有人能看到的评论。\n·您可以随时解锁此缺陷跟踪 。 - 解锁意味着:\n·每个人都可以再次对这个缺陷跟踪发表评论。\n·您可以随时再次锁定此缺陷跟踪。\n + 锁定意味着:\n + ·其他用户无法为此 Issue 添加新评论;\n + ·您和其他有写入权限的协作者仍然可以留下所有人都能看到的评论;\n + ·您可以随时解锁此 Issue;\n + + 解锁意味着:\n + ·每个人都可以对这个 Issue 发表评论;\n + ·您可以随时再次锁定此 Issue.\n + 锁定 解锁 - 关闭缺陷跟踪出错,请稍后再试。 - 重新打开缺陷跟踪出错, 请稍后再试。 - 已关闭缺陷跟踪 -

没有提供描述

+ 关闭 Issue 时发生错误, 请稍后再试. + 重新打开 Issue 时发生错误, 请稍后再试. + Issue 已关闭 + 无描述 一级标题 二级标题 三级标题 @@ -66,131 +96,146 @@ 添加 变化 状态 - 确定吗? + 确定? 成功 - 删除评论出错 + 删除评论时发生错误 删除 评论 评论 已合并 - 文件 文件菜单 文件 下载 返回 上级文件夹 代码查看器 - 用浏览器打开 + 在浏览器中打开 大文件 - 该文件太大,无法打开。\n触摸“确定”下载。 + 该文件太大, 无法在线预览.\n点按"确定"下载. 观众 提交 在此输入内容 描述 文件名 带扩展名的文件名 - 私有代码片段 - 公共代码片段 + 私有 Gist + 公开 Gist 提交为 删除 - 删除代码片段出错 + 删除 Gist 时发生错误 没有文件 - 必填项目 + 必填 已提交 - 创建代码片段 + 创建 Gist 清除 用户 标题 + 文件 里程碑 分配给自己 - 提交缺陷跟踪 - 创建缺陷跟踪出错 - 创建缺陷跟踪 - 请取消选择以继续编辑。 + 提交 Issue + 创建 Issue 时发生错误 + 创建 Issue + 请取消选择以继续编辑. 通知 你有未读的通知 - 打开的 + 打开 + 在新窗口中打开 通知类型 标签 没有标签 - 已添加标签 + 标签已添加 提交反馈 注销 - 感谢您的反馈 ! + 感谢您的反馈! 当前版本 版本 支持开发, 启用广告 用户名 密码 - 双因素认证码 - 登陆 - 代码片段描述 - 触摸头像打开用户的个人资料 - 长按一个分支打开上游或该分支仓库。 - 下载此版本 + 两步验证 + 登录 + Gist 描述 + 点按头像打开用户的个人资料 + 长按 Fork 消息以打开上游仓库或此 Fork 的仓库. + 下载 选项 下载或分享 - 收藏/取消收藏 此仓库 - 关注 - 关注/取消关注 此仓库 - 添加书签可以从导航栏快速访问它们。 - 找不到URL + 点按评论以标记其作者或编辑你的评论.\n长按你的评论以删除. + Star/取消 Star 此仓库 + Watch + Watch/取消 Watch 此仓库 + 加入书签以从导航栏快速访问. + 忽略全部 + 找不到网址 最后更新 预览 预览 - 预览Markdown文本显示效果\n\n左右滑动Markdown编辑器图标以获取更多选项。 + 切换 Markdown 语法高亮. \n滑动 Markdown 编辑器图标可查看更多选项. 创建日期 - 文件创建时间 - 最后更新时间 + 文件的创建时间 + 文件的最后更新时间 全部标记为已读 所有通知 未读 全部 删除仓库 - 删除此仓库后不能撤销 + 删除操作无法撤销 30 分钟 20 分钟 10 分钟 5 分钟 1 分钟 + 1 小时 + 2 小时 + 3 小时 已创建 已提交 已下载 已关注 - 缺陷跟踪 的评论 + 评论 Issue 成员 - 拉取请求 的评论 - 推送给 - 小组 + 评论 Pull Request + Push 到 + 团队 已删除 未知 - 提交记录 的评论 + 评论 Commit 切换分支 - 受理人 + 受理者 修改 - 更新缺陷跟踪 - 更新拉取请求 + \u2022 已修改 + 更新 Issue + 更新 Pull Request 没有里程碑 添加 完成 主页 - 创建里程碑 - 创建里程碑出错 + 建立里程碑 + 建立里程碑时发生错误 截止于 - 没有受理人 + 没有受理者 - 提交记录已切换到所选分支 + Commit 已被切换到所选分支 一般 - 更改FastHub检查通知的频率 + 更改 FastHub 检查通知的频率 通知同步间隔 所有 行为 + 自定义 启用列表动画 列表动画 - 禁用确认退出的Toast + 禁用用于防止意外退出的提示框 返回键直接退出 - 任何未保存的更改都将被丢弃 + 恢复 + 备份 + 备份成功! + 选择要还原的备份 + 没有权限. + 最后更新: %s + 现在 + 丢弃所有未保存的更改? 私人 使用圆角矩形头像代替圆形头像 矩形头像 @@ -201,63 +246,65 @@ 关于 FastHub 的问题 反馈 问题反馈 - 有问题吗? 在这里报告 + 遇到问题? 在此反馈 关于 通知 - 关掉 + 关闭 未登录 - 需要两个认证因素 - 没有缺陷跟踪 + 需要两步验证 + 没有 Issue 复制 已复制 提交说明 与服务器通信时发生错误 - 请求API时出现意外错误 - 请求服务器发生错误,请稍后再试 + 调用 API 时出现意外错误 + 请求服务器发生错误, 请稍后再试 将通知标记为已读 - 派生此代码片段 - 使用默认的浏览器登陆 (OAuth) + Fork 此 Gist + 使用默认浏览器登录 (OAuth) - 关闭通知读取状态 - 点击通知后,禁止标记为已读。 + 禁用通知已读状态 + 点击通知后不再自动标记为已读. 选择主题 - 选择默认使用的主题 + 选择默认主题 + 选择主题强调色 + 主题强调色 网站 支持开发 - 非常感谢! - 如果主题没有正常显示,请重新打开此应用。 + 非常感谢! + 如果主题没有正常显示, 请手动重启此应用. 书签 书签 删除书签 - 空空如也,给仓库添加书签就可以在这里查看了 - 确定 - 没有 + 没有书签. \n注: 书签按访问频率降序排列. + + 没有动态 - 没有代码片段 + 没有 Gist 没有评论 没有通知 - 没有被人关注 + 没有关注者 没有关注的人 没有仓库 - 没有已收藏的仓库 - 没有提交记录 + 没有已 Star 的仓库 + 没有 Commit 没有贡献者 - 没有发布版本 - 没有关闭的缺陷跟踪 - 没有打开的缺陷跟踪 + 没有 Releases + 没有关闭的 Issue + 没有打开的 Issue 没有事件 - 没有打开的拉取请求 - 没有关闭的拉取请求 + 没有打开的 Pull Request + 没有关闭的 Pull Request 没有搜索结果 - 请接受权限请求,以便 FastHub 将文件存储在设备上 - 公共代码片段 + 请接受权限请求, 允许 FastHub 在设备上储存文件 + 公共 Gist 开启广告 - 没有缺陷跟踪 - 没有未读的通知。 - 我的代码片段 + 没有 Issue + 没有未读通知 + 我的 Gist 更新日志 - 点击打开通知列表或滑动以关闭 - 长按可从任何地方导航到主屏幕 + 点按通知列表以查看或滑动以忽略 + 长按可从任何地方跳转到主屏幕 已创建 被分配 提到我 @@ -267,11 +314,12 @@ 组织 组织 成员 - 小组 + 团队 成员 没有成员 - 没有小组 + 没有团队 没有加入组织 + 找不到你所属的组织? 标记为已读 动画 启用弹出动画 @@ -283,112 +331,135 @@ 所有检查通过 排序 最新的 - 最老的 + 最旧的 最多评论 最少评论 最近更新 - 最早更新 + 最久没更新 + 已是最新版本! + 有新版本可用 搜索内容不能为空 - 长按创建缺陷跟踪 - 该拉取请求可以合并 - 审核 + 长按创建 Issue + 该 Pull Request 可以被合并 + 已审核 驳回 - 批准 - 没有反映 - 反映 + 认同这些更改 + 没有反馈 + 反馈 自动换行 在代码查看器中默认自动换行 自动换行 - 软件许可 - 收到通知后,发出声音。 - 开启通知声音 - 用个人令牌登录 - 个人令牌 - 使用基本认证登录 - 如果你实际是某个组织的成员,但你找不到组织,请访问下面的链接。\nhttps://help.github.com/articles/about-oauth-app-access-restrictions\nPS:您可以使用访问令牌登录,这将授予FastHub权限以查看您的组织列表。 + 开源许可 + 收到通知时发出声音. + 启用通知声音 + 启用通知 + 用 Personal Token 登录 + Personal Token + 使用 Basic Auth 登录 + + 如果你实际是某个组织的成员, 但在此没有显示, 请访问下面的链接:\n + https://help.github.com/articles/about-oauth-app-access-restrictions\n + 注: 您可以使用 Access token 登录, 这将授予 FastHub 查看您的组织列表的权限. + 插入 选择 选择图片 - 支持 2.00$ - 支持 5.00$ - 支持 10.00$ - 支持 20.00$ + 捐赠 $2.00 + 捐赠 $5.00 + 捐赠 $10.00 + 捐赠 $20.00$ 语言 + 语言 选择语言 - 选择您喜欢的语言 - 取消订阅 - 访问令牌 - 添加评论 - 应用 - 已分配 - 已备份! - 备份 - 最后更新:%s - 简介横幅 - 基本认证 - 选择登录方式 - 贡献 - 自定义 - 丢弃 - 跳过全部 - 选择横幅 - • 已修改 - 启用通知 - 始终签名 - 每次选择是否签名 - 在文本编辑器下每次选择是否签名后发送 - 始终签名后发送 - 无法加载图像。 - 动态 - 文件 - 过滤器 + 选择您希望 FastHub 使用的语言 + 取消订阅 - 加载图片时出错,请重试。 - 加入 Slack - 你想加入 FastHub Slack 群组吗? - 语言 - 已添加里程碑 - 有新版本可用。 - 没有审核者 - 没有趋势 - 找不到用户 - 由于 GitHub 的限制,按 Emoji 排序功能异常 - 现在 - 1 小时 - 已加入 + 订阅 + 这个仓库禁用了 Issue + Access Token + Basic Authentication + 选择登录方式 + 文件 路径 - 未授予许可。 - 付费主题 - 回复 - 这个仓库关闭了缺陷跟踪 - 重置 - 恢复 审核请求 - 审核者已添加 + 加入 Slack + 你想加入 FastHub Slack 群组吗? + 已邀请 + 回复 + 无法加载图像. + 想要合并 + 关注者 审核者 - 向下滚动 - 向上滑动 - 选择要还原的备份 + 没有审核者 使用 %2$s%3$s 从我的 %1$s 发送 - 排序方式 - 订阅 - 已邀请 + 始终签名 + 始终签名后发送 + 每次选择是否签名 + 在编辑器下选择每次是否签名 标签 - 选择强调色调 - 主题强调色 - 3 小时 - 已勾选的复选框 - 未勾选的复选框 + 添加评论 + 简介横幅 + + 从 FastHub 2.5.0 开始, 您可以在个人页面使用横幅来更好的展示自己. \n\n + 任何使用 FastHub 应用的人都会看到您的横幅, 您也可以看到其他人的横幅! + 如果您想为自己创建一个横幅, 确保其分辨率为 1280x384 或其他等比例大小, 否则显示时可能会被裁剪.\n\n + 您可以按照以下步骤随意设置或更改您的横幅: 创建一个描述为 "header.fst", 内容为横幅图片的直链链接的 Gist.\n\n + 或还可以更简单, 直接使用内置的图片选择器! + + 选择横幅 + 加载图片时出错, 请重试. 趋势 - 2 小时 + 由于 GitHub 的限制, 无法正常按 Emoji 排序 + 向上滑动 + 向下滑动 + 已加入 + 找不到用户 + 没有趋势 + 重置 + 应用 + 过滤器 类型 - 已经是最新版本! - 想要合并 - 关注者 - 触摸评论来标记其作者或编辑您的评论。\n\n长按你的评论删除它。 - 分割线 - 使用FastHub 2.5.0,您现在可以更好地为自己的个人资料页面展示横幅。\n\n任何使用FastHub应用的人都会看到您的标题,您也可以看到其他人的标题! 如果你想为自己创建一个横幅,使它成为1280x384或其它等比大小,否则可能会被裁剪。\n\n您可以随时添加或更改您的横幅,通过创建一个描述为“header.fst”的 Gist,其中包含一个有横幅链接的文件。\n\n或者更简单,只需使用内置的图像选择器! - 草案 - 选择代码主题 + 排序方式 + 已分配 + 审核者已添加 + 已添加里程碑 + 动态 + 付费主题 + 选择代码高亮主题 + + 请登录你的 Github 帐号以使用 Github API 提供的功能, + 否则你将只能使用 Enterprise Github 的功能, + 因为 Github API 使用的 Token 是来自于你的 Enterprise 帐号的. + + 添加帐号 + 选择帐号 + 不匹配 + 警告 + 所有者 + 楼主 + 取消审核 + 导航栏不再随主题颜色 + 导航栏默认样式 + 选择自定义通知提示音 + 选择通知提示音 + 禁止自动播放 GIF + 禁止播放 GIF + 需要更改 + Google Play 服务不可用 + 编辑 Gist + 内容 + 展开 + 复制 SHA-1 + 作为代码查看 + 禁用 APP 内动画 + 关闭所有 APP 内动画. + 此 Pull Request 目前无法合并. + 项目 + 无项目 + 无卡片 + %s 于 %s 新增 + 更改内容过多. 请在浏览器中查看. + 项目 + 常见问题
From 85ba2346819e40dd9dd3bd142de6163c8b21d6ba Mon Sep 17 00:00:00 2001 From: yakov116 Date: Mon, 25 Sep 2017 08:38:22 -0400 Subject: [PATCH 09/36] Language updates --- app/src/main/res/values/strings.xml | 34 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f750da9ad..416275c42 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -92,14 +92,14 @@ • All PRO Themes\n • PR Reviews & On-line code comments (PRs & Commits)\n • Commit from FastHub (Edit, Create & Delete files)\n - • Support to other Merge methods (Rebase & Squash)\n + • Supports 2 more Merging methods (Rebase & Squash)\n • Login to unlimited accounts\n • Edit & Add unlimited Gist Files\n • Project Columns & Cards (Edit, Create & Delete) - • New upcoming PRO features + • New upcoming PRO features (Coming soon...) ]]> Purchase @@ -124,7 +124,7 @@ Unlock Unlock Everything Feeds - + Loading, please wait… Action @@ -134,9 +134,9 @@ OK No data available Search - Please sign in to continue using FastHub + Please sign in to begin using FastHub Sign in using your GitHub account to use FastHub - Failed to sign in + Sign in failed Sign in Share Reload @@ -230,7 +230,7 @@ No files Required field Successfully submitted - Create Gist + Create a Gist Clear Users Title @@ -553,7 +553,7 @@ Disable auto playing GIFs Disable Playing GIF requested changes - Google Play Service unavailable + Google Play Service's are unavailable Edit Gist Content expand @@ -578,24 +578,24 @@
• I tried to login via Access Token & OTP but it does not work?

You can\'t login via Access Token & OTP all together due to the lifetime of the OTP code, you\'ll be required to login in every few seconds.

-
• Why my Private Repo & Enterprise Wiki does not show up?
-

It\'s due to FastHub scraping GitHub Wiki page & Private Repos require session token that FastHub doesn\'t have.

+
• Why are my Private Repo & Enterprise Wiki not showing up?
+

It\'s due to FastHub scraping GitHub Wiki page & Private Repos require session token that FastHub is unable to obtain.

• I login with Enterprise account but can\'t interact with anything other than my Enterprise GitHub?
\n -

Well, logically, you can\'t access anything else other than your Enterprise, but FastHub made that possible but can\'t do much about it, - in most cases since your login credential doesn\'t exists in GitHub server. But in few +

Well, logically, you can\'t access anything else other than your Enterprise. FastHub tries to allow as much possible, but can\'t do much about it + in most cases, since your login credential doesn\'t exists in GitHub server. But in a few cases your GitHub account Oauth token will do the trick.

-
• I\'m having problems editing Issues/PRs?
+
• Why am I having problems editing Issues/PRs?

If you are editing a public Org repo, then please contact your Org to grant access to FastHub or use Access Token to login!.

• I\'m blocked from using FastHub, WHY?
-

Simply, because you did rate FastHub low because you wanted this and that and never updated your review, well, - why would you keep using it anyway?

+

Simply, because you have rated FastHub low and requested things via the play store and after I responded you did not update your review. If the app is no good, + why would you want to keep using it anyway?

• I\'m having this issue or I want this & that!!

Head to https://github.com/k0shk0sh/FastHub/issues/new and create new issue for bugs or feature requests, I really do encourage you to - search before posting something that is duplicated as that will result to it being closed immediately.

+ search before opening a ticket. Any duplicate request will result in it being closed immediately.

Message from the developer of FastHub,\n @@ -606,7 +606,7 @@ ignored.\nAs developers I think we understand how much effort does it take to make an App like FastHub.\nSome don\'t get that at all and instead of reporting issues/features in FastHub\'s repo they rate FastHub low in the PlayStore thinking that this will force the developer (ME) to implement and fix their stuff so they update their review, but this doesn\'t happen, instead they either never - update their review or they\'ll request something else (so typical).\nPlease do report Issues & Features in the FastHub repo, open + update their review or they\'ll request something else (so typical).\nPlease report Issues & Feature requests in the FastHub repo, open up the drawer menu & click on the Report Issue & it\'ll be posted directly to FastHub repo where I can help/assist you.

]]> From 1e35375fe68f8b3032cb02b8371ac78c849cb42e Mon Sep 17 00:00:00 2001 From: yakov116 Date: Tue, 26 Sep 2017 09:48:42 -0400 Subject: [PATCH 10/36] Update strings.xml Fixed --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 416275c42..814c3a77c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -553,7 +553,7 @@ Disable auto playing GIFs Disable Playing GIF requested changes - Google Play Service's are unavailable + Google Play Services are unavailable Edit Gist Content expand From 47fb3c2ed7ebe367a471dbbbf853ba1248da5ecc Mon Sep 17 00:00:00 2001 From: Astro36 Date: Thu, 28 Sep 2017 16:04:56 +0900 Subject: [PATCH 11/36] Improve the korean translation --- app/src/main/res/values-ko/strings.xml | 52 +++++++++++++++----------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index b7a184c7f..85ea36159 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -139,7 +139,7 @@ 저장소 즐겨찾기/즐겨찾기해제 구독 저장소 구독/구독해제 - 사이드바에서 더 빨리 액세스 할 수 있도록 저장소를 고정하세요 + 사이드바에서 더 빨리 액세스 할 수 있도록 저장소를 바로가기에 등록하세요 모두 닫기 URL을 찾을 수 없습니다 마지막 업데이트 @@ -153,7 +153,7 @@ 모두 읽음으로 표시 모든 알림 읽지 않음 - 모든 + 전체 저장소 삭제 저장소 삭제는 되돌릴 수 없습니다 30분 @@ -247,13 +247,13 @@ 개발 지원 대단히 감사합니다! 테마가 제대로 적용되지 않으면, 앱을 수동으로 재시작해주세요 - 고정 - 고정됨 - 고정해제 - 아직 고정된 저장소가 없으므로 여기에서 볼 수 있도록 고정하세요.\nP.S: 많이 액세스할수록 저장소는 위에 배치될 것입니다. + 북마크 + 바로가기 + 바로가기 삭제 + 아직 바로가기 저장소가 없으므로 여기에서 볼 수 있도록 등록하세요.\nP.S: 많이 액세스할수록 저장소는 위에 배치될 것입니다. 아니요 - Feeds 없음 + 소식 없음 Gists 없음 덧글 없음 알림 없음 @@ -279,9 +279,9 @@ 변경사항 클릭하여 알림 목록을 열거나 옆으로 밀어 닫으세요 길게 누르면 어디서나 기본 화면으로 이동합니다 - 생성됨 - 담당됨 - 언급됨 + 생성 + 담당 + 언급 이름 색상 꼬리표 샹성 @@ -293,7 +293,7 @@ 구성원 없음 팀 없음 조직 없음 - 너의 조직을 찾을 수 없습니까? + 조직을 찾을 수 없습니까? 읽음으로 표시 효과 팝업 효과를 활성화합니다 @@ -321,7 +321,7 @@ 반응 없음 반응 줄 바꿈 - 기본적으로 코드 뷰어에서 코드 줄 바꿈 + 기본적으로 코드 뷰어에서 코드 줄 바꿈을 실행합니다 코드 줄 바꿈 오픈소스 라이브러리 알림음을 활성화합니다 @@ -330,15 +330,15 @@ 개인 토큰으로 로그인 개인 토큰 기본 인증으로 로그인 - 실제로 조직에 속해 있고 여기에서 볼 수 없다면 다음 링크를 따르십시오. + 조직에 속해 있고 여기에서 볼 수 없다면 다음 링크를 따르세요. \nhttps://help.github.com/articles/about-third-party-application-restrictions\nPS: 조직에 FastHub 엑세스 권한을 부여하고 액세스 토큰을 사용해서 로그인할 수 있습니다.\n또한 https://github.com/settings/applications에서 FastHub를 찾아 조직 액세스로 스크롤 한 다음 승인 버튼을 클릭하세요. 삽입 선택 사진 선택 - $2.00 지원 - $5.00 지원 - $10.00 지원 - $20.00 지원 + $2.00 기부 + $5.00 기부 + $10.00 기부 + $20.00 기부 언어 언어 언어 선택 @@ -347,7 +347,7 @@ from in 구독 - 이 저장소에서 이슈가 사용 중지되었습니다 + 이슈가 비활성화된 저장소입니다 엑세스 토큰 기본 인증 로그인 종류 선택 @@ -381,10 +381,10 @@ 배너 선택 이미지 불러오기 오류, 다시 시도하세요 급상승 - GitHub 제한 때문에 emojies로 정렬이 실제로 작동하지 않습니다 + GitHub 제한 때문에 emojies로 정렬이 작동하지 않습니다 위로 스크롤 아래로 스크롤 - 참여됨 + 참여 모두 선택됨 모두 선택 취소됨 구분선 @@ -398,9 +398,9 @@ 담당자 추가 성공 검토자 추가 성공 이정표 추가 성공 - Feed + 소식 프리미엄 테마 - 코드 색상 표 + 문법 강조기 테마 GitHub API의 모든 항목에 액세스하려면 GitHub 계정에 로그인하십시오. 그렇지 않으면 GitHub API로 전송 된 토큰은 엔터프라이즈 계정에서 전송되기 때문에 Enterprise GitHub가 아닌 다른 항목에 액세스 할 때마다 강제 로그아웃 될 수 있습니다. @@ -427,4 +427,12 @@ 앱 애니메이션 모든 앱 애니메이션을 비활성화합니다 이 풀 리퀘스트는 현재 병합될 수 없습니다 + 프로젝트 + 프로젝트 없음 + 카드 없음 + %s %s가 추가함 + 변경사항이 너무 많습니다. 브라우저로 보세요 + 프로젝트 + 읽어주세요 + 자주하는 질문 From 6398706605cc52a7f6436dd3287bb0a2e97999c9 Mon Sep 17 00:00:00 2001 From: Astro36 Date: Thu, 28 Sep 2017 16:09:53 +0900 Subject: [PATCH 12/36] Fix mis typo --- app/src/main/res/values-ko/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 85ea36159..e291578a5 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -284,7 +284,7 @@ 언급 이름 색상 - 꼬리표 샹성 + 꼬리표 생성 조직 조직 구성원 From ddc41e36a79028e52ea3eb190a47396e62dc12a0 Mon Sep 17 00:00:00 2001 From: k0shk0sh Date: Fri, 29 Sep 2017 18:38:31 +0200 Subject: [PATCH 13/36] this commit fixes #1024 fixes #1024 fixes #998 fixes #986 --- README.md | 2 + app/src/main/AndroidManifest.xml | 22 +- .../main/graphql/github/RepoProject.graphql | 55 +++++ .../data/dao/CommitFileChanges.java | 11 +- .../fastaccess/data/dao/CommitFileModel.java | 85 +++++++- .../fastaccess/data/dao/CommitLinesModel.java | 60 +++++- .../data/dao/FragmentPagerAdapterModel.java | 3 +- .../data/dao/model/AbstractUser.java | 2 + .../java/com/fastaccess/helper/Bundler.java | 1 - .../com/fastaccess/helper/FileHelper.java | 2 +- .../provider/markdown/MarkDownProvider.java | 26 +-- .../PullRequestFilesViewHolder.java | 7 +- .../ui/base/BaseDialogFragment.java | 3 +- .../popup/EditorLinkImageDialogFragment.java | 6 +- .../org/OrgProfileOverviewFragment.java | 32 ++- .../profile/org/project/OrgProjectActivity.kt | 63 ++++++ .../ui/modules/repos/RepoPagerPresenter.java | 2 +- .../comments/CommitCommentsFragment.java | 17 +- .../projects/RepoProjectsFragmentPager.kt | 4 +- .../projects/columns/ProjectColumnFragment.kt | 6 + .../projects/details/ProjectPagerActivity.kt | 28 ++- .../projects/details/ProjectPagerPresenter.kt | 52 +++-- .../projects/list/RepoProjectFragment.kt | 2 +- .../projects/list/RepoProjectPresenter.kt | 197 ++++++++++++------ .../files/PullRequestFilesFragment.java | 24 +++ .../details/files/PullRequestFilesMvp.java | 2 + .../files/PullRequestFilesPresenter.java | 4 +- .../FullScreenFileChangeActivity.kt | 168 +++++++++++++++ .../fullscreen/FullScreenFileChangeMvp.kt | 21 ++ .../FullScreenFileChangePresenter.kt | 37 ++++ .../reviews/AddReviewDialogFragment.kt | 6 +- .../dialog/ProgressDialogFragment.java | 7 +- .../ui/widgets/markdown/MarkDownLayout.kt | 24 ++- .../full_screen_file_changes_layout.xml | 80 +++++++ .../layout/org_profile_overview_layout.xml | 20 ++ app/src/main/res/values/strings.xml | 3 +- 36 files changed, 933 insertions(+), 151 deletions(-) create mode 100644 app/src/main/java/com/fastaccess/ui/modules/profile/org/project/OrgProjectActivity.kt create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/fullscreen/FullScreenFileChangeActivity.kt create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/fullscreen/FullScreenFileChangeMvp.kt create mode 100644 app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/fullscreen/FullScreenFileChangePresenter.kt create mode 100644 app/src/main/res/layouts/main_layouts/layout/full_screen_file_changes_layout.xml diff --git a/README.md b/README.md index c93c55698..7e437c556 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,11 @@ Yet another **open-source** GitHub client app but unlike any other app, FastHub - FastHub & GitHub Pinned Repos - Trending - Wiki + - Projects - **Repositories** - Browse & Read Wiki - Edit, Create & Delete files (commit) + - Edit, Create & Delete files (Project Columns Cards) - Search Repos - Browse and search Repos - See your public, private and forked Repos diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1867eb844..fad56395d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -213,12 +213,6 @@ android:configChanges="keyboard|orientation|screenSize" android:theme="@style/ThemeTranslucent"/> - - + + + + + + + + - constructToObservable(@Nullable List files) { + public static Observable constructToObservable(@Nullable ArrayList files) { if (files == null || files.isEmpty()) return Observable.empty(); - return Observable.fromIterable(files).map(CommitFileChanges::getCommitFileChanges); + return Observable.fromIterable(construct(files)); } @NonNull public static List construct(@Nullable List files) { @@ -66,4 +66,11 @@ private CommitFileChanges(Parcel in) { @Override public CommitFileChanges[] newArray(int size) {return new CommitFileChanges[size];} }; + + public static boolean canAttachToBundle(CommitFileChanges model) { + Parcel parcel = Parcel.obtain(); + model.writeToParcel(parcel, 0); + int size = parcel.dataSize(); + return size < 600000; + } } diff --git a/app/src/main/java/com/fastaccess/data/dao/CommitFileModel.java b/app/src/main/java/com/fastaccess/data/dao/CommitFileModel.java index 78db70cff..b2a86f6af 100644 --- a/app/src/main/java/com/fastaccess/data/dao/CommitFileModel.java +++ b/app/src/main/java/com/fastaccess/data/dao/CommitFileModel.java @@ -3,14 +3,9 @@ import android.os.Parcel; import android.os.Parcelable; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - /** * Created by Kosh on 01 Jan 2017, 9:00 PM */ -@Getter @Setter @NoArgsConstructor public class CommitFileModel implements Parcelable { private String sha; @@ -72,4 +67,84 @@ public class CommitFileModel implements Parcelable { ", patch='" + patch + '\'' + '}'; } + + public String getSha() { + return sha; + } + + public void setSha(String sha) { + this.sha = sha; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public int getAdditions() { + return additions; + } + + public void setAdditions(int additions) { + this.additions = additions; + } + + public int getDeletions() { + return deletions; + } + + public void setDeletions(int deletions) { + this.deletions = deletions; + } + + public int getChanges() { + return changes; + } + + public void setChanges(int changes) { + this.changes = changes; + } + + public String getBlobUrl() { + return blobUrl; + } + + public void setBlobUrl(String blobUrl) { + this.blobUrl = blobUrl; + } + + public String getRawUrl() { + return rawUrl; + } + + public void setRawUrl(String rawUrl) { + this.rawUrl = rawUrl; + } + + public String getContentsUrl() { + return contentsUrl; + } + + public void setContentsUrl(String contentsUrl) { + this.contentsUrl = contentsUrl; + } + + public String getPatch() { + return patch; + } + + public void setPatch(String patch) { + this.patch = patch; + } } diff --git a/app/src/main/java/com/fastaccess/data/dao/CommitLinesModel.java b/app/src/main/java/com/fastaccess/data/dao/CommitLinesModel.java index f1c59295e..89e9df3a6 100644 --- a/app/src/main/java/com/fastaccess/data/dao/CommitLinesModel.java +++ b/app/src/main/java/com/fastaccess/data/dao/CommitLinesModel.java @@ -12,8 +12,6 @@ import java.util.regex.Matcher; import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; import static com.fastaccess.ui.widgets.DiffLineSpan.HUNK_TITLE; @@ -21,7 +19,7 @@ * Created by Kosh on 20 Jun 2017, 7:32 PM */ -@Getter @Setter @AllArgsConstructor public class CommitLinesModel implements Parcelable { +@AllArgsConstructor public class CommitLinesModel implements Parcelable { public static final int TRANSPARENT = 0; public static final int ADDITION = 1; @@ -115,4 +113,60 @@ private CommitLinesModel(Parcel in) { @Override public CommitLinesModel[] newArray(int size) {return new CommitLinesModel[size];} }; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public int getColor() { + return color; + } + + public void setColor(int color) { + this.color = color; + } + + public int getLeftLineNo() { + return leftLineNo; + } + + public void setLeftLineNo(int leftLineNo) { + this.leftLineNo = leftLineNo; + } + + public int getRightLineNo() { + return rightLineNo; + } + + public void setRightLineNo(int rightLineNo) { + this.rightLineNo = rightLineNo; + } + + public boolean isNoNewLine() { + return noNewLine; + } + + public void setNoNewLine(boolean noNewLine) { + this.noNewLine = noNewLine; + } + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } + + public boolean isHasCommentedOn() { + return hasCommentedOn; + } + + public void setHasCommentedOn(boolean hasCommentedOn) { + this.hasCommentedOn = hasCommentedOn; + } } diff --git a/app/src/main/java/com/fastaccess/data/dao/FragmentPagerAdapterModel.java b/app/src/main/java/com/fastaccess/data/dao/FragmentPagerAdapterModel.java index 980993908..4d6b40374 100644 --- a/app/src/main/java/com/fastaccess/data/dao/FragmentPagerAdapterModel.java +++ b/app/src/main/java/com/fastaccess/data/dao/FragmentPagerAdapterModel.java @@ -2,6 +2,7 @@ import android.content.Context; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import com.annimon.stream.Collectors; @@ -239,7 +240,7 @@ public FragmentPagerAdapterModel(String title, Fragment fragment, String key) { .toList(); } - @NonNull public static List buildForRepoProjects(@NonNull Context context, @NonNull String repoId, + @NonNull public static List buildForRepoProjects(@NonNull Context context, @Nullable String repoId, @NonNull String login) { return Stream.of(new FragmentPagerAdapterModel(context.getString(R.string.open), RepoProjectFragment.Companion.newInstance(login, repoId, IssueState.open)), diff --git a/app/src/main/java/com/fastaccess/data/dao/model/AbstractUser.java b/app/src/main/java/com/fastaccess/data/dao/model/AbstractUser.java index ee5f84b6a..06c44fd7a 100644 --- a/app/src/main/java/com/fastaccess/data/dao/model/AbstractUser.java +++ b/app/src/main/java/com/fastaccess/data/dao/model/AbstractUser.java @@ -19,6 +19,7 @@ import io.requery.Key; import io.requery.Persistable; import io.requery.Table; +import io.requery.Transient; import lombok.NoArgsConstructor; import static com.fastaccess.data.dao.model.User.FOLLOWER_NAME; @@ -68,6 +69,7 @@ public abstract class AbstractUser implements Parcelable { @Column(name = "date_column") Date date; String repoId; String description; + @Transient boolean hasOrganizationProjects; public void save(User entity) { if (getUser(entity.getId()) != null) { diff --git a/app/src/main/java/com/fastaccess/helper/Bundler.java b/app/src/main/java/com/fastaccess/helper/Bundler.java index 409819f0a..f0cb1f848 100644 --- a/app/src/main/java/com/fastaccess/helper/Bundler.java +++ b/app/src/main/java/com/fastaccess/helper/Bundler.java @@ -200,5 +200,4 @@ private Bundle get() { return get(); } - } diff --git a/app/src/main/java/com/fastaccess/helper/FileHelper.java b/app/src/main/java/com/fastaccess/helper/FileHelper.java index e9a35c37a..2b281396d 100644 --- a/app/src/main/java/com/fastaccess/helper/FileHelper.java +++ b/app/src/main/java/com/fastaccess/helper/FileHelper.java @@ -62,7 +62,7 @@ public class FileHelper { title = cur.getString(cur.getColumnIndex(MediaStore.Audio.Media.TITLE)); } } - } + } catch (Exception ignored) {} } } return title; diff --git a/app/src/main/java/com/fastaccess/provider/markdown/MarkDownProvider.java b/app/src/main/java/com/fastaccess/provider/markdown/MarkDownProvider.java index 83aa7c0a2..4a7efc615 100644 --- a/app/src/main/java/com/fastaccess/provider/markdown/MarkDownProvider.java +++ b/app/src/main/java/com/fastaccess/provider/markdown/MarkDownProvider.java @@ -10,6 +10,7 @@ import com.annimon.stream.IntStream; import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.Logger; import com.fastaccess.provider.markdown.extension.emoji.EmojiExtension; import com.fastaccess.provider.markdown.extension.mention.MentionExtension; import com.fastaccess.provider.timeline.HtmlHelper; @@ -256,19 +257,11 @@ public static void addDivider(@NonNull EditText editText) { } - public static void addPhoto(@NonNull EditText editText) { - addLink(editText, "", ""); - } - public static void addPhoto(@NonNull EditText editText, @NonNull String title, @NonNull String link) { String result = "![" + InputHelper.toString(title) + "](" + InputHelper.toString(link) + ")"; insertAtCursor(editText, result); } - public static void addLink(@NonNull EditText editText) { - addLink(editText, "", ""); - } - public static void addLink(@NonNull EditText editText, @NonNull String title, @NonNull String link) { String result = "[" + InputHelper.toString(title) + "](" + InputHelper.toString(link) + ")"; insertAtCursor(editText, result); @@ -319,10 +312,17 @@ public static boolean isArchive(@Nullable String name) { public static void insertAtCursor(@NonNull EditText editText, @NonNull String text) { String oriContent = editText.getText().toString(); - int index = editText.getSelectionStart() >= 0 ? editText.getSelectionStart() : 0; - StringBuilder builder = new StringBuilder(oriContent); - builder.insert(index, text); - editText.setText(builder.toString()); - editText.setSelection(index + text.length()); + int start = editText.getSelectionStart(); + int end = editText.getSelectionEnd(); + int index = start >= 0 ? start : 0; + Logger.e(start, end); + if (start >= 0 && end > 0) { + editText.setText(editText.getText().replace(start, end, text)); + } else { + StringBuilder builder = new StringBuilder(oriContent); + builder.insert(index, text); + editText.setText(builder.toString()); + editText.setSelection(index + text.length()); + } } } diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestFilesViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestFilesViewHolder.java index 2b0077f8a..105c95bf3 100644 --- a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestFilesViewHolder.java +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestFilesViewHolder.java @@ -108,8 +108,13 @@ private void onToggle(boolean expanded, boolean animate, int position) { if (model.getLinesModel().size() <= 100) { patch.setAdapter(new CommitLinesAdapter(model.getLinesModel(), this)); patch.setVisibility(View.VISIBLE); + } else if (CommitFileChanges.canAttachToBundle(model)) { + if (adapter.getListener() != null) { + //noinspection unchecked + adapter.getListener().onItemClick(position, patch, model); + } } else { - Toasty.warning(itemView.getContext(),itemView.getResources().getString(R.string.too_large_changes)).show(); + Toasty.warning(itemView.getContext(), itemView.getResources().getString(R.string.too_large_changes)).show(); return; } } else { diff --git a/app/src/main/java/com/fastaccess/ui/base/BaseDialogFragment.java b/app/src/main/java/com/fastaccess/ui/base/BaseDialogFragment.java index 1893e6119..1ae34cce6 100644 --- a/app/src/main/java/com/fastaccess/ui/base/BaseDialogFragment.java +++ b/app/src/main/java/com/fastaccess/ui/base/BaseDialogFragment.java @@ -22,6 +22,7 @@ import com.fastaccess.helper.PrefGetter; import com.fastaccess.ui.base.mvp.BaseMvp; import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import com.fastaccess.ui.widgets.dialog.ProgressDialogFragment; import net.grandcentrix.thirtyinch.TiDialogFragment; @@ -103,7 +104,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Dialog dialog = super.onCreateDialog(savedInstanceState); - if (!PrefGetter.isAppAnimationDisabled()) { + if (!PrefGetter.isAppAnimationDisabled() && !(this instanceof ProgressDialogFragment)) { dialog.setOnShowListener(dialogInterface -> AnimHelper.revealDialog(dialog, getResources().getInteger(android.R.integer.config_longAnimTime))); } diff --git a/app/src/main/java/com/fastaccess/ui/modules/editor/popup/EditorLinkImageDialogFragment.java b/app/src/main/java/com/fastaccess/ui/modules/editor/popup/EditorLinkImageDialogFragment.java index 0d0549ad7..8dba5f1d9 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/editor/popup/EditorLinkImageDialogFragment.java +++ b/app/src/main/java/com/fastaccess/ui/modules/editor/popup/EditorLinkImageDialogFragment.java @@ -38,11 +38,12 @@ public class EditorLinkImageDialogFragment extends BaseDialogFragment>() { + + @State var org: String? = null + + @BindView(R.id.appbar) lateinit var appBar: AppBarLayout + + override fun layout(): Int = R.layout.activity_fragment_layout + + override fun isTransparent(): Boolean = true + + override fun canBack(): Boolean = true + + override fun isSecured(): Boolean = false + + override fun providePresenter(): BasePresenter = BasePresenter() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + appBar.elevation = 0f + appBar.stateListAnimator = null + if (savedInstanceState == null) { + org = intent.extras.getString(BundleConstant.ITEM) + val org = org + if (org != null) { + supportFragmentManager.beginTransaction() + .replace(R.id.container, RepoProjectsFragmentPager.newInstance(org), + RepoProjectsFragmentPager.TAG) + .commit() + } + } + toolbar?.apply { subtitle = org } + } + + companion object { + fun startActivity(context: Context, org: String, isEnterprise: Boolean) { + val intent = Intent(context, OrgProjectActivity::class.java) + intent.putExtras(Bundler.start().put(BundleConstant.ITEM, org) + .put(BundleConstant.IS_ENTERPRISE, isEnterprise) + .end()) + context.startActivity(intent) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerPresenter.java index 51827e4fd..964f7c27e 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerPresenter.java +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/RepoPagerPresenter.java @@ -236,7 +236,7 @@ private void callApi(int navTyp) { break; case RepoPagerMvp.PROJECTS: if (projectsFragmentPager == null) { - onAddAndHide(fragmentManager, RepoProjectsFragmentPager.Companion.newInstance(repoId(), login()), currentVisible); + onAddAndHide(fragmentManager, RepoProjectsFragmentPager.Companion.newInstance(login(), repoId()), currentVisible); } else { onShowHideFragment(fragmentManager, projectsFragmentPager, currentVisible); } diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsFragment.java b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsFragment.java index 8d0a2bbeb..c699ac4d3 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsFragment.java +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/code/commit/details/comments/CommitCommentsFragment.java @@ -188,9 +188,20 @@ public static CommitCommentsFragment newInstance(@NonNull String login, @NonNull } @Override public void onTagUser(@Nullable User user) { - if (commentsCallback != null && user != null) { - commentsCallback.onTagUser(user.getLogin()); - } + Intent intent = new Intent(getContext(), EditorActivity.class); + intent.putExtras(Bundler + .start() + .put(BundleConstant.ID, getPresenter().repoId()) + .put(BundleConstant.EXTRA_TWO, getPresenter().login()) + .put(BundleConstant.EXTRA_THREE, getPresenter().sha()) + .put(BundleConstant.EXTRA, user != null ? "@" + user.getLogin() : "") + .put(BundleConstant.EXTRA_TYPE, BundleConstant.ExtraType.NEW_COMMIT_COMMENT_EXTRA) + .putStringArrayList("participants", CommentsHelper.getUsersByTimeline(adapter.getData())) + .put(BundleConstant.IS_ENTERPRISE, isEnterprise()) + .end()); + View view = getActivity() != null && getActivity().findViewById(R.id.fab) != null ? getActivity().findViewById(R.id.fab) : recycler; + ActivityHelper.startReveal(this, intent, view, BundleConstant.REQUEST_CODE); + } @Override public void onReply(User user, String message) { diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/projects/RepoProjectsFragmentPager.kt b/app/src/main/java/com/fastaccess/ui/modules/repos/projects/RepoProjectsFragmentPager.kt index 61467b61f..91fcd3e56 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/projects/RepoProjectsFragmentPager.kt +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/projects/RepoProjectsFragmentPager.kt @@ -39,7 +39,7 @@ class RepoProjectsFragmentPager : BaseFragment? = null private val adapter by lazy { ColumnCardAdapter(presenter.getCards(), isOwner()) } @@ -119,6 +122,9 @@ class ProjectColumnFragment : BaseFragment { - if (!presenter.login.isBlank() && !presenter.repoId.isBlank()) { - val nameParse = NameParser("") - nameParse.name = presenter.repoId - nameParse.username = presenter.login - nameParse.isEnterprise = isEnterprise - RepoPagerActivity.startRepoPager(this, nameParse) + val repoId = presenter.repoId + if (repoId != null && !repoId.isNullOrBlank()) { + if (!presenter.login.isBlank()) { + val nameParse = NameParser("") + nameParse.name = presenter.repoId + nameParse.username = presenter.login + nameParse.isEnterprise = isEnterprise + RepoPagerActivity.startRepoPager(this, nameParse) + } + } else if (!presenter.login.isBlank()) { + UserPagerActivity.startActivity(this, presenter.login, true, isEnterprise, 0) } finish() true @@ -112,7 +118,11 @@ class ProjectPagerActivity : BaseActivity(), ProjectPage private val columns = arrayListOf() @com.evernote.android.state.State var projectId: Long = -1 - @com.evernote.android.state.State var repoId: String = "" + @com.evernote.android.state.State var repoId: String? = null @com.evernote.android.state.State var login: String = "" @com.evernote.android.state.State var viewerCanUpdate: Boolean = false @@ -27,23 +27,39 @@ class ProjectPagerPresenter : BasePresenter(), ProjectPage override fun onRetrieveColumns() { - makeRestCall(Observable.zip(RestProvider.getProjectsService(isEnterprise).getProjectColumns(projectId), - RestProvider.getRepoService(isEnterprise).isCollaborator(login, repoId, Login.getUser().login), - BiFunction { items: Pageable, response: Response -> - viewerCanUpdate = response.code() == 204 - return@BiFunction items - }) - .flatMap { - if (it.items != null) { - return@flatMap Observable.just(it.items) - } - return@flatMap Observable.just(listOf()) - }, - { t -> - columns.clear() - columns.addAll(t) - sendToView { it.onInitPager(columns) } - }) + val repoId = repoId + if (repoId != null && !repoId.isNullOrBlank()) { + makeRestCall(Observable.zip(RestProvider.getProjectsService(isEnterprise).getProjectColumns(projectId), + RestProvider.getRepoService(isEnterprise).isCollaborator(login, repoId, Login.getUser().login), + BiFunction { items: Pageable, response: Response -> + viewerCanUpdate = response.code() == 204 + return@BiFunction items + }) + .flatMap { + if (it.items != null) { + return@flatMap Observable.just(it.items) + } + return@flatMap Observable.just(listOf()) + }, + { t -> + columns.clear() + columns.addAll(t) + sendToView { it.onInitPager(columns) } + }) + } else { + makeRestCall(RestProvider.getProjectsService(isEnterprise).getProjectColumns(projectId) + .flatMap { + if (it.items != null) { + return@flatMap Observable.just(it.items) + } + return@flatMap Observable.just(listOf()) + }, + { t -> + columns.clear() + columns.addAll(t) + sendToView { it.onInitPager(columns) } + }) + } } override fun onActivityCreated(intent: Intent?) { diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/projects/list/RepoProjectFragment.kt b/app/src/main/java/com/fastaccess/ui/modules/repos/projects/list/RepoProjectFragment.kt index 057e5a381..0deb05810 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/projects/list/RepoProjectFragment.kt +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/projects/list/RepoProjectFragment.kt @@ -134,7 +134,7 @@ class RepoProjectFragment : BaseFragment(), RepoProjectMv private var previousTotal: Int = 0 private var lastPage = Integer.MAX_VALUE @com.evernote.android.state.State var login: String = "" - @com.evernote.android.state.State var repoId: String = "" + @com.evernote.android.state.State var repoId: String? = null var count: Int = 0 val pages = arrayListOf() @@ -65,74 +68,152 @@ class RepoProjectPresenter : BasePresenter(), RepoProjectMv return false } currentPage = page + Logger.e(login) + val repoId = repoId val apollo = ApolloProdivder.getApollo(isEnterprise) - if (parameter == IssueState.open) { - val query = RepoProjectsOpenQuery.builder() - .name(repoId) - .owner(login) - .page(getPage()) - .build() - makeRestCall(Rx2Apollo.from(apollo.query(query)) - .flatMap { - val list = arrayListOf() - it.data()?.repository()?.let { - it.projects().let { - lastPage = if (it.pageInfo().hasNextPage()) Int.MAX_VALUE else 0 - pages.clear() - count = it.totalCount() - it.edges()?.let { - pages.addAll(it.map { it.cursor() }) + if (repoId != null && !repoId.isNullOrBlank()) { + if (parameter == IssueState.open) { + val query = RepoProjectsOpenQuery.builder() + .name(repoId) + .owner(login) + .page(getPage()) + .build() + makeRestCall(Rx2Apollo.from(apollo.query(query)) + .flatMap { + val list = arrayListOf() + it.data()?.repository()?.let { + it.projects().let { + lastPage = if (it.pageInfo().hasNextPage()) Int.MAX_VALUE else 0 + pages.clear() + count = it.totalCount() + it.edges()?.let { + pages.addAll(it.map { it.cursor() }) + } + it.nodes()?.let { + list.addAll(it) + } } - it.nodes()?.let { - list.addAll(it) + } + return@flatMap Observable.just(list) + }, + { + sendToView({ v -> + v.onNotifyAdapter(it, page) + if (page == 1) v.onChangeTotalCount(count) + }) + }) + } else { + val query = RepoProjectsClosedQuery.builder() + .name(repoId) + .owner(login) + .page(getPage()) + .build() + makeRestCall(Rx2Apollo.from(apollo.query(query)) + .flatMap { + val list = arrayListOf() + it.data()?.repository()?.let { + it.projects().let { + lastPage = if (it.pageInfo().hasNextPage()) Int.MAX_VALUE else 0 + pages.clear() + count = it.totalCount() + it.edges()?.let { + pages.addAll(it.map { it.cursor() }) + } + it.nodes()?.let { + val toConvert = arrayListOf() + it.onEach { + val columns = RepoProjectsOpenQuery.Columns(it.columns().__typename(), it.columns().totalCount()) + val node = RepoProjectsOpenQuery.Node(it.__typename(), it.name(), it.number(), it.body(), + it.createdAt(), it.id(), it.viewerCanUpdate(), columns, it.databaseId()) + toConvert.add(node) + } + list.addAll(toConvert) + } } } - } - return@flatMap Observable.just(list) - }, - { - sendToView({ v -> - v.onNotifyAdapter(it, page) - if (page == 1) v.onChangeTotalCount(count) + return@flatMap Observable.just(list) + }, + { + sendToView({ v -> + v.onNotifyAdapter(it, page) + if (page == 1) v.onChangeTotalCount(count) + }) }) - }) + } } else { - val query = RepoProjectsClosedQuery.builder() - .name(repoId) - .owner(login) - .page(getPage()) - .build() - makeRestCall(Rx2Apollo.from(apollo.query(query)) - .flatMap { - val list = arrayListOf() - it.data()?.repository()?.let { - it.projects().let { - lastPage = if (it.pageInfo().hasNextPage()) Int.MAX_VALUE else 0 - pages.clear() - count = it.totalCount() - it.edges()?.let { - pages.addAll(it.map { it.cursor() }) + if (parameter == IssueState.open) { + val query = OrgProjectsOpenQuery.builder() + .owner(login) + .page(getPage()) + .build() + makeRestCall(Rx2Apollo.from(apollo.query(query)) + .flatMap { + val list = arrayListOf() + it.data()?.organization()?.let { + it.projects().let { + lastPage = if (it.pageInfo().hasNextPage()) Int.MAX_VALUE else 0 + pages.clear() + count = it.totalCount() + it.edges()?.let { + pages.addAll(it.map { it.cursor() }) + } + it.nodes()?.let { + val toConvert = arrayListOf() + it.onEach { + val columns = RepoProjectsOpenQuery.Columns(it.columns().__typename(), it.columns().totalCount()) + val node = RepoProjectsOpenQuery.Node(it.__typename(), it.name(), it.number(), it.body(), + it.createdAt(), it.id(), it.viewerCanUpdate(), columns, it.databaseId()) + toConvert.add(node) + } + list.addAll(toConvert) + } } - it.nodes()?.let { - val toConvert = arrayListOf() - it.onEach { - val columns = RepoProjectsOpenQuery.Columns(it.columns().__typename(), it.columns().totalCount()) - val node = RepoProjectsOpenQuery.Node(it.__typename(), it.name(), it.number(), it.body(), - it.createdAt(), it.id(), it.viewerCanUpdate(), columns, it.databaseId()) - toConvert.add(node) + } + return@flatMap Observable.just(list) + }, + { + sendToView({ v -> + v.onNotifyAdapter(it, page) + if (page == 1) v.onChangeTotalCount(count) + }) + }) + } else { + val query = OrgProjectsClosedQuery.builder() + .owner(login) + .page(getPage()) + .build() + makeRestCall(Rx2Apollo.from(apollo.query(query)) + .flatMap { + val list = arrayListOf() + it.data()?.organization()?.let { + it.projects().let { + lastPage = if (it.pageInfo().hasNextPage()) Int.MAX_VALUE else 0 + pages.clear() + count = it.totalCount() + it.edges()?.let { + pages.addAll(it.map { it.cursor() }) + } + it.nodes()?.let { + val toConvert = arrayListOf() + it.onEach { + val columns = RepoProjectsOpenQuery.Columns(it.columns().__typename(), it.columns().totalCount()) + val node = RepoProjectsOpenQuery.Node(it.__typename(), it.name(), it.number(), it.body(), + it.createdAt(), it.id(), it.viewerCanUpdate(), columns, it.databaseId()) + toConvert.add(node) + } + list.addAll(toConvert) } - list.addAll(toConvert) } } - } - return@flatMap Observable.just(list) - }, - { - sendToView({ v -> - v.onNotifyAdapter(it, page) - if (page == 1) v.onChangeTotalCount(count) + return@flatMap Observable.just(list) + }, + { + sendToView({ v -> + v.onNotifyAdapter(it, page) + if (page == 1) v.onChangeTotalCount(count) + }) }) - }) + } } return true } diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/PullRequestFilesFragment.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/PullRequestFilesFragment.java index cb907c98b..7ef7952d8 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/PullRequestFilesFragment.java +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/PullRequestFilesFragment.java @@ -1,6 +1,8 @@ package com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.files; +import android.app.Activity; import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -24,6 +26,7 @@ import com.fastaccess.ui.base.BaseFragment; import com.fastaccess.ui.modules.main.premium.PremiumActivity; import com.fastaccess.ui.modules.repos.issues.issue.details.IssuePagerMvp; +import com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.files.fullscreen.FullScreenFileChangeActivity; import com.fastaccess.ui.modules.reviews.AddReviewDialogFragment; import com.fastaccess.ui.widgets.FontTextView; import com.fastaccess.ui.widgets.StateLayout; @@ -169,6 +172,10 @@ private void setupChanges() { return onLoadMore; } + @Override public void onOpenForResult(int position, @NonNull CommitFileChanges model) { + FullScreenFileChangeActivity.Companion.startActivityForResult(this, model, position); + } + @Override public void onRefresh() { getPresenter().onCallApi(1, null); } @@ -232,6 +239,23 @@ private void setupChanges() { } } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK) { + if (requestCode == FullScreenFileChangeActivity.Companion.getFOR_RESULT_CODE() && data != null) { + List comments = data.getParcelableArrayListExtra(BundleConstant.ITEM); + if (comments != null && !comments.isEmpty()) { + if (viewCallback != null) { + for (CommentRequestModel comment : comments) { + viewCallback.onAddComment(comment); + } + showMessage(R.string.success, R.string.comments_added_successfully); + } + } + } + } + super.onActivityResult(requestCode, resultCode, data); + } + private void showReload() { hideProgress(); stateLayout.showReload(adapter.getItemCount()); diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/PullRequestFilesMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/PullRequestFilesMvp.java index 24123bbdd..a75bc1d18 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/PullRequestFilesMvp.java +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/PullRequestFilesMvp.java @@ -29,6 +29,8 @@ interface View extends BaseMvp.FAView, SwipeRefreshLayout.OnRefreshListener, and void onNotifyAdapter(@Nullable List items, int page); @NonNull OnLoadMore getLoadMore(); + + void onOpenForResult(int position, @NonNull CommitFileChanges linesModel); } interface Presenter extends BaseMvp.FAPresenter, diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/PullRequestFilesPresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/PullRequestFilesPresenter.java index ab1f40bff..4f3d126ef 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/PullRequestFilesPresenter.java +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/PullRequestFilesPresenter.java @@ -104,7 +104,9 @@ class PullRequestFilesPresenter extends BasePresenter } @Override public void onItemClick(int position, View v, CommitFileChanges model) { - if (v.getId() == R.id.open) { + if (v.getId() == R.id.patchList) { + sendToView(view -> view.onOpenForResult(position, model)); + } else if (v.getId() == R.id.open) { CommitFileModel item = model.getCommitFileModel(); PopupMenu popup = new PopupMenu(v.getContext(), v); MenuInflater inflater = popup.getMenuInflater(); diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/fullscreen/FullScreenFileChangeActivity.kt b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/fullscreen/FullScreenFileChangeActivity.kt new file mode 100644 index 000000000..0e46dd7cc --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/fullscreen/FullScreenFileChangeActivity.kt @@ -0,0 +1,168 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.files.fullscreen + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.support.v4.app.Fragment +import android.support.v4.widget.SwipeRefreshLayout +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.TextView +import butterknife.BindView +import com.fastaccess.R +import com.fastaccess.data.dao.CommentRequestModel +import com.fastaccess.data.dao.CommitFileChanges +import com.fastaccess.data.dao.CommitLinesModel +import com.fastaccess.helper.BundleConstant +import com.fastaccess.helper.Bundler +import com.fastaccess.helper.PrefGetter +import com.fastaccess.ui.adapter.CommitLinesAdapter +import com.fastaccess.ui.base.BaseActivity +import com.fastaccess.ui.modules.main.premium.PremiumActivity +import com.fastaccess.ui.modules.reviews.AddReviewDialogFragment +import com.fastaccess.ui.widgets.StateLayout +import com.fastaccess.ui.widgets.recyclerview.DynamicRecyclerView +import com.fastaccess.ui.widgets.recyclerview.scroll.RecyclerViewFastScroller + +/** + * Created by Hashemsergani on 24.09.17. + */ + +class FullScreenFileChangeActivity : BaseActivity(), FullScreenFileChangeMvp.View { + + @BindView(R.id.recycler) lateinit var recycler: DynamicRecyclerView + @BindView(R.id.refresh) lateinit var refresh: SwipeRefreshLayout + @BindView(R.id.stateLayout) lateinit var stateLayout: StateLayout + @BindView(R.id.fastScroller) lateinit var fastScroller: RecyclerViewFastScroller + @BindView(R.id.changes) lateinit var changes: TextView + @BindView(R.id.deletion) lateinit var deletion: TextView + @BindView(R.id.addition) lateinit var addition: TextView + + val commentList = arrayListOf() + + private val adapter by lazy { CommitLinesAdapter(arrayListOf(), this) } + + override fun layout(): Int = R.layout.full_screen_file_changes_layout + + override fun isTransparent(): Boolean = false + + override fun canBack(): Boolean = true + + override fun isSecured(): Boolean = false + + override fun providePresenter(): FullScreenFileChangePresenter = FullScreenFileChangePresenter() + + override fun onNotifyAdapter(model: CommitLinesModel) { + adapter.addItem(model) + } + + override fun showMessage(titleRes: Int, msgRes: Int) { + hideProgress() + super.showMessage(titleRes, msgRes) + } + + override fun showMessage(titleRes: String, msgRes: String) { + hideProgress() + super.showMessage(titleRes, msgRes) + } + + override fun showErrorMessage(msgRes: String) { + hideProgress() + super.showErrorMessage(msgRes) + } + + override fun showProgress(resId: Int) { + stateLayout.showProgress() + refresh.isRefreshing = true + } + + override fun hideProgress() { + stateLayout.hideProgress() + refresh.isRefreshing = false + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + refresh.isEnabled = false + recycler.adapter = adapter + val padding = resources.getDimensionPixelSize(R.dimen.spacing_normal) + recycler.setPadding(padding, 0, padding, 0) + fastScroller.attachRecyclerView(recycler) + presenter.onLoad(intent) + presenter.model?.let { model -> + title = Uri.parse(model.commitFileModel.filename).lastPathSegment + addition.text = model.commitFileModel?.additions.toString() + deletion.text = model.commitFileModel?.deletions.toString() + changes.text = model.commitFileModel?.changes.toString() + } + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.done_menu, menu) + menu?.findItem(R.id.submit)?.setIcon(R.drawable.ic_done) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + return when (item?.itemId) { + R.id.submit -> { + val intent = Intent() + intent.putExtras(Bundler.start().putParcelableArrayList(BundleConstant.ITEM, commentList).end()) + setResult(Activity.RESULT_OK, intent) + finish() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun onItemClick(position: Int, v: View, item: CommitLinesModel) { + if (item.text.startsWith("@@")) return + val commit = presenter.model?.commitFileModel ?: return + if (PrefGetter.isProEnabled()) { + AddReviewDialogFragment.newInstance(item, Bundler.start() + .put(BundleConstant.ITEM, commit.filename) + .put(BundleConstant.EXTRA_TWO, presenter.position) + .put(BundleConstant.EXTRA_THREE, position) + .end()) + .show(supportFragmentManager, "AddReviewDialogFragment") + } else { + PremiumActivity.startActivity(this) + } + } + + override fun onItemLongClick(position: Int, v: View?, item: CommitLinesModel?) { + + } + + override fun onCommentAdded(comment: String, item: CommitLinesModel, bundle: Bundle?) { + if (bundle != null) { + val path = bundle.getString(BundleConstant.ITEM) ?: return + val commentRequestModel = CommentRequestModel() + commentRequestModel.body = comment + commentRequestModel.path = path + commentRequestModel.position = item.position + commentList.add(commentRequestModel) + val childPosition = bundle.getInt(BundleConstant.EXTRA_THREE) + val current = adapter.getItem(childPosition) + if (current != null) { + current.isHasCommentedOn = true + adapter.swapItem(current, childPosition) + } + } + } + + companion object { + val FOR_RESULT_CODE = 1002 + fun startActivityForResult(fragment: Fragment, model: CommitFileChanges, position: Int) { + val intent = Intent(fragment.context, FullScreenFileChangeActivity::class.java) + intent.putExtras(Bundler.start() + .put(BundleConstant.EXTRA, model) + .put(BundleConstant.ITEM, position) + .end()) + fragment.startActivityForResult(intent, FOR_RESULT_CODE) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/fullscreen/FullScreenFileChangeMvp.kt b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/fullscreen/FullScreenFileChangeMvp.kt new file mode 100644 index 000000000..398eedff4 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/fullscreen/FullScreenFileChangeMvp.kt @@ -0,0 +1,21 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.files.fullscreen + +import android.content.Intent +import com.fastaccess.data.dao.CommitLinesModel +import com.fastaccess.ui.base.mvp.BaseMvp +import com.fastaccess.ui.modules.reviews.callback.ReviewCommentListener +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder + +/** + * Created by Hashemsergani on 24.09.17. + */ + +interface FullScreenFileChangeMvp { + interface View : BaseMvp.FAView, BaseViewHolder.OnItemClickListener, ReviewCommentListener { + fun onNotifyAdapter(model: CommitLinesModel) + } + + interface Presenter { + fun onLoad(intent: Intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/fullscreen/FullScreenFileChangePresenter.kt b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/fullscreen/FullScreenFileChangePresenter.kt new file mode 100644 index 000000000..6c60e5762 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/pull_requests/pull_request/details/files/fullscreen/FullScreenFileChangePresenter.kt @@ -0,0 +1,37 @@ +package com.fastaccess.ui.modules.repos.pull_requests.pull_request.details.files.fullscreen + +import android.content.Intent +import com.fastaccess.data.dao.CommitFileChanges +import com.fastaccess.helper.BundleConstant +import com.fastaccess.helper.RxHelper +import com.fastaccess.ui.base.mvp.presenter.BasePresenter +import io.reactivex.Observable + +/** + * Created by Hashemsergani on 24.09.17. + */ +class FullScreenFileChangePresenter : BasePresenter(), FullScreenFileChangeMvp.Presenter { + + var model: CommitFileChanges? = null + var position: Int = -1 + + override fun onLoad(intent: Intent) { + intent.extras?.let { + position = it.getInt(BundleConstant.ITEM) + model = it.getParcelable(BundleConstant.EXTRA) + } + model?.let { + manageDisposable(RxHelper.getObservable(Observable.fromIterable(it.linesModel)) + .doOnSubscribe({ sendToView { it.showProgress(0) } }) + .flatMap { Observable.just(it) } + .subscribe + ({ + sendToView { v -> v.onNotifyAdapter(it) } + }, { + onError(it) + }, { + sendToView { it.hideProgress() } + })) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/modules/reviews/AddReviewDialogFragment.kt b/app/src/main/java/com/fastaccess/ui/modules/reviews/AddReviewDialogFragment.kt index ea599c0d3..8996204d9 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/reviews/AddReviewDialogFragment.kt +++ b/app/src/main/java/com/fastaccess/ui/modules/reviews/AddReviewDialogFragment.kt @@ -39,10 +39,10 @@ class AddReviewDialogFragment : BaseDialogFragment TransitionManager.beginDelayedTransition(this) if (editText.isEnabled && !InputHelper.isEmpty(editText)) { editText.isEnabled = false + selectionIndex = editText.selectionEnd MarkDownProvider.setMdText(editText, InputHelper.toString(editText)) editorIconsHolder.visibility = View.INVISIBLE addEmojiView.visibility = View.INVISIBLE ViewHelper.hideKeyboard(editText) } else { editText.setText(it.getSavedText()) - editText.setSelection(editText.text.length) + editText.setSelection(selectionIndex) editText.isEnabled = true editorIconsHolder.visibility = View.VISIBLE addEmojiView.visibility = View.VISIBLE @@ -87,9 +88,9 @@ class MarkDownLayout : LinearLayout { Snackbar.make(this, R.string.error_highlighting_editor, Snackbar.LENGTH_SHORT).show() } else { when { - v.id == R.id.link -> EditorLinkImageDialogFragment.newInstance(true) + v.id == R.id.link -> EditorLinkImageDialogFragment.newInstance(true, getSelectedText()) .show(it.fragmentManager(), "EditorLinkImageDialogFragment") - v.id == R.id.image -> EditorLinkImageDialogFragment.newInstance(false) + v.id == R.id.image -> EditorLinkImageDialogFragment.newInstance(false, getSelectedText()) .show(it.fragmentManager(), "EditorLinkImageDialogFragment") v.id == R.id.addEmoji -> { ViewHelper.hideKeyboard(it.getEditText()) @@ -119,8 +120,6 @@ class MarkDownLayout : LinearLayout { R.id.header -> MarkDownProvider.addDivider(editText) R.id.code -> MarkDownProvider.addCode(editText) R.id.quote -> MarkDownProvider.addQuote(editText) - R.id.link -> MarkDownProvider.addLink(editText) - R.id.image -> MarkDownProvider.addPhoto(editText) R.id.checkbox -> MarkDownProvider.addList(editText, "- [x]") R.id.unCheckbox -> MarkDownProvider.addList(editText, "- [ ]") R.id.inlineCode -> MarkDownProvider.addInlinleCode(editText) @@ -162,4 +161,15 @@ class MarkDownLayout : LinearLayout { } } } + + fun getSelectedText(): String? { + markdownListener?.getEditText()?.let { + if (!it.text.toString().isBlank()) { + val selectionStart = it.selectionStart + val selectionEnd = it.selectionEnd + return it.text.toString().substring(selectionStart, selectionEnd) + } + } + return null + } } \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/full_screen_file_changes_layout.xml b/app/src/main/res/layouts/main_layouts/layout/full_screen_file_changes_layout.xml new file mode 100644 index 000000000..db01da078 --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/full_screen_file_changes_layout.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/org_profile_overview_layout.xml b/app/src/main/res/layouts/row_layouts/layout/org_profile_overview_layout.xml index c7a9bbf78..ffc615d47 100644 --- a/app/src/main/res/layouts/row_layouts/layout/org_profile_overview_layout.xml +++ b/app/src/main/res/layouts/row_layouts/layout/org_profile_overview_layout.xml @@ -77,6 +77,7 @@ android:gravity="center|start" android:paddingBottom="@dimen/spacing_xs_large" android:paddingTop="@dimen/spacing_xs_large" + android:visibility="gone" tools:text="Cum classis nocere"/> @@ -92,6 +93,7 @@ android:gravity="center|start" android:paddingBottom="@dimen/spacing_xs_large" android:paddingTop="@dimen/spacing_xs_large" + android:visibility="gone" tools:text="Cum classis nocere"/> @@ -120,8 +123,25 @@ android:gravity="center|start" android:paddingBottom="@dimen/spacing_xs_large" android:paddingTop="@dimen/spacing_xs_large" + android:visibility="gone" tools:text="Cum classis nocere"/> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e21b0e286..0c4a16393 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -154,7 +154,7 @@ User Details Archive file detected, please download the file to view its content. - Minimum characters (3) + Minimum characters (2) No file found No readme found Downloading… @@ -611,4 +611,5 @@

]]>
FAQ + Comments added successfully From e095e990f71fb783f9f4abcabcc85fa2a0a237df Mon Sep 17 00:00:00 2001 From: k0shk0sh Date: Sun, 1 Oct 2017 09:47:35 +0200 Subject: [PATCH 14/36] this commit fixes #1037 fixes #1034 fixes #1017 closes #996 --- .../provider/timeline/HtmlHelper.java | 7 + .../timeline/handler/HeaderHandler.kt | 33 +++ .../timeline/handler/QouteHandler.java | 4 +- .../viewholder/IssueDetailsViewHolder.java | 26 ++- .../ui/modules/main/MainActivity.java | 1 - .../layout/issue_detail_header_row_item.xml | 211 ++++++++++-------- app/src/main/res/values/strings.xml | 17 -- build.gradle | 6 +- 8 files changed, 191 insertions(+), 114 deletions(-) create mode 100644 app/src/main/java/com/fastaccess/provider/timeline/handler/HeaderHandler.kt diff --git a/app/src/main/java/com/fastaccess/provider/timeline/HtmlHelper.java b/app/src/main/java/com/fastaccess/provider/timeline/HtmlHelper.java index 2a8df9e37..99e9ad1bc 100644 --- a/app/src/main/java/com/fastaccess/provider/timeline/HtmlHelper.java +++ b/app/src/main/java/com/fastaccess/provider/timeline/HtmlHelper.java @@ -19,6 +19,7 @@ import com.fastaccess.provider.timeline.handler.BetterLinkMovementExtended; import com.fastaccess.provider.timeline.handler.DrawableHandler; import com.fastaccess.provider.timeline.handler.EmojiHandler; +import com.fastaccess.provider.timeline.handler.HeaderHandler; import com.fastaccess.provider.timeline.handler.HrHandler; import com.fastaccess.provider.timeline.handler.ItalicHandler; import com.fastaccess.provider.timeline.handler.LinkHandler; @@ -106,6 +107,12 @@ private static HtmlSpanner initHtml(@NonNull TextView textView, int width) { mySpanner.registerHandler("hr", new HrHandler(windowBackground, width, false)); mySpanner.registerHandler("emoji", new EmojiHandler()); mySpanner.registerHandler("mention", new LinkHandler()); + mySpanner.registerHandler("h1", new HeaderHandler(1.5F)); + mySpanner.registerHandler("h2", new HeaderHandler(1.4F)); + mySpanner.registerHandler("h3", new HeaderHandler(1.3F)); + mySpanner.registerHandler("h4", new HeaderHandler(1.2F)); + mySpanner.registerHandler("h5", new HeaderHandler(1.1F)); + mySpanner.registerHandler("h6", new HeaderHandler(1.0F)); if (width > 0) { TableHandler tableHandler = new TableHandler(); tableHandler.setTextColor(ViewHelper.generateTextColor(windowBackground)); diff --git a/app/src/main/java/com/fastaccess/provider/timeline/handler/HeaderHandler.kt b/app/src/main/java/com/fastaccess/provider/timeline/handler/HeaderHandler.kt new file mode 100644 index 000000000..32c61f8ee --- /dev/null +++ b/app/src/main/java/com/fastaccess/provider/timeline/handler/HeaderHandler.kt @@ -0,0 +1,33 @@ +package com.fastaccess.provider.timeline.handler + +import android.text.SpannableStringBuilder +import android.text.style.RelativeSizeSpan +import net.nightwhistler.htmlspanner.TagNodeHandler +import net.nightwhistler.htmlspanner.spans.FontFamilySpan +import org.htmlcleaner.TagNode + +/** + * Created by Kosh on 29.09.17. + */ +class HeaderHandler(val size: Float) : TagNodeHandler() { + + override fun beforeChildren(node: TagNode?, builder: SpannableStringBuilder?) { + appendNewLine(builder) + } + + override fun handleTagNode(node: TagNode, builder: SpannableStringBuilder, start: Int, end: Int) { + builder.setSpan(RelativeSizeSpan(this.size), start, end, 33) + val originalSpan = this.getFontFamilySpan(builder, start, end) + val boldSpan: FontFamilySpan + if (originalSpan == null) { + boldSpan = FontFamilySpan(this.spanner.defaultFont) + } else { + boldSpan = FontFamilySpan(originalSpan.fontFamily) + boldSpan.isItalic = originalSpan.isItalic + } + + boldSpan.isBold = true + builder.setSpan(boldSpan, start, end, 33) + appendNewLine(builder) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/provider/timeline/handler/QouteHandler.java b/app/src/main/java/com/fastaccess/provider/timeline/handler/QouteHandler.java index 60fdcdd42..2b098cec0 100644 --- a/app/src/main/java/com/fastaccess/provider/timeline/handler/QouteHandler.java +++ b/app/src/main/java/com/fastaccess/provider/timeline/handler/QouteHandler.java @@ -21,8 +21,10 @@ public void handleTagNode(TagNode node, SpannableStringBuilder builder, int start, int end) { builder.append("\n"); - builder.setSpan(new MarkDownQuoteSpan(color), start, builder.length(), 33); + builder.setSpan(new MarkDownQuoteSpan(color), start + 1, builder.length() - 1, 33); builder.append("\n"); } + + } diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueDetailsViewHolder.java b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueDetailsViewHolder.java index 8e51f713a..440776b4c 100644 --- a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueDetailsViewHolder.java +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/IssueDetailsViewHolder.java @@ -1,5 +1,6 @@ package com.fastaccess.ui.adapter.viewholder; +import android.graphics.Color; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.transition.ChangeBounds; @@ -10,6 +11,7 @@ import android.widget.TextView; import com.fastaccess.R; +import com.fastaccess.data.dao.LabelModel; import com.fastaccess.data.dao.ReactionsModel; import com.fastaccess.data.dao.TimelineModel; import com.fastaccess.data.dao.model.Issue; @@ -17,6 +19,7 @@ import com.fastaccess.data.dao.model.User; import com.fastaccess.helper.InputHelper; import com.fastaccess.helper.ParseDateFormat; +import com.fastaccess.helper.ViewHelper; import com.fastaccess.provider.scheme.LinkParserHelper; import com.fastaccess.provider.timeline.CommentsHelper; import com.fastaccess.provider.timeline.HtmlHelper; @@ -25,11 +28,13 @@ import com.fastaccess.ui.adapter.callback.ReactionsCallback; import com.fastaccess.ui.widgets.AvatarLayout; import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.LabelSpan; import com.fastaccess.ui.widgets.SpannableBuilder; import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter; import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder; import java.util.Date; +import java.util.List; import butterknife.BindView; @@ -56,6 +61,8 @@ public class IssueDetailsViewHolder extends BaseViewHolder { @BindView(R.id.emojiesList) View emojiesList; @BindView(R.id.reactionsText) TextView reactionsText; @BindView(R.id.owner) TextView owner; + @BindView(R.id.labels) TextView labels; + @BindView(R.id.labelsHolder) View labelsHolder; private OnToggleView onToggleView; private ReactionsCallback reactionsCallback; private ViewGroup viewGroup; @@ -172,11 +179,13 @@ private void addReactionCount(View v) { private void bind(@NonNull Issue issueModel) { setup(issueModel.getUser(), issueModel.getBodyHtml(), issueModel.getReactions()); setupDate(issueModel.getCreatedAt(), issueModel.getUpdatedAt()); + setupLabels(issueModel.getLabels()); } private void bind(@NonNull PullRequest pullRequest) { setup(pullRequest.getUser(), pullRequest.getBodyHtml(), pullRequest.getReactions()); setupDate(pullRequest.getCreatedAt(), pullRequest.getUpdatedAt()); + setupLabels(pullRequest.getLabels()); } private void setup(User user, String description, ReactionsModel reactionsModel) { @@ -194,7 +203,7 @@ private void setup(User user, String description, ReactionsModel reactionsModel) appendEmojies(reactionsModel); } if (!InputHelper.isEmpty(description)) { - HtmlHelper.htmlIntoTextView(comment, description, viewGroup.getWidth()); + HtmlHelper.htmlIntoTextView(comment, description, viewGroup.getWidth() - ViewHelper.dpToPx(itemView.getContext(), 24)); } else { comment.setText(R.string.no_description_provided); } @@ -204,6 +213,21 @@ private void setupDate(@NonNull Date createdDate, @NonNull Date updated) { date.setText(ParseDateFormat.getTimeAgo(createdDate)); } + private void setupLabels(@Nullable List labelList) { + if (labelList != null && !labelList.isEmpty()) { + SpannableBuilder builder = SpannableBuilder.builder(); + for (LabelModel labelModel : labelList) { + int color = Color.parseColor("#" + labelModel.getColor()); + builder.append(" ").append(" " + labelModel.getName() + " ", new LabelSpan(color)); + } + labels.setText(builder); + labelsHolder.setVisibility(View.VISIBLE); + } else { + labels.setText(""); + labelsHolder.setVisibility(View.GONE); + } + } + private void appendEmojies(@NonNull ReactionsModel reaction) { SpannableBuilder spannableBuilder = SpannableBuilder.builder(); reactionsText.setText(""); diff --git a/app/src/main/java/com/fastaccess/ui/modules/main/MainActivity.java b/app/src/main/java/com/fastaccess/ui/modules/main/MainActivity.java index e3b7f20eb..e50ec8a9b 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/main/MainActivity.java +++ b/app/src/main/java/com/fastaccess/ui/modules/main/MainActivity.java @@ -34,7 +34,6 @@ import it.sephiroth.android.library.bottomnavigation.BottomNavigation; import shortbread.Shortcut; -@Shortcut(id = "feeds", icon = R.drawable.ic_app_shortcut_github, shortLabelRes = R.string.feeds, rank = 1) public class MainActivity extends BaseActivity implements MainMvp.View { @State @MainMvp.NavigationType int navType = MainMvp.FEEDS; diff --git a/app/src/main/res/layouts/row_layouts/layout/issue_detail_header_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/issue_detail_header_row_item.xml index 66969400a..4509ae6b9 100644 --- a/app/src/main/res/layouts/row_layouts/layout/issue_detail_header_row_item.xml +++ b/app/src/main/res/layouts/row_layouts/layout/issue_detail_header_row_item.xml @@ -1,134 +1,163 @@ - + android:orientation="vertical"> - + android:layout_marginBottom="@dimen/spacing_normal" + android:layout_marginEnd="@dimen/grid_spacing" + android:layout_marginStart="@dimen/grid_spacing" + android:layout_marginTop="@dimen/grid_spacing" + android:background="?card_background" + android:paddingBottom="@dimen/spacing_normal" + android:paddingTop="@dimen/spacing_normal" + tools:ignore="RtlSymmetry"> - - + android:orientation="vertical"> + android:layout_marginEnd="@dimen/spacing_micro" + android:orientation="horizontal"> + + + android:layout_gravity="center" + android:layout_marginEnd="@dimen/spacing_normal" + android:layout_weight="1" + android:orientation="vertical"> - + android:orientation="horizontal"> + + + + + + - + android:visibility="gone" + tools:text="@string/owner" + tools:visibility="visible"/> - + + + android:background="?selectableItemBackgroundBorderless" + android:contentDescription="@string/options" + android:padding="@dimen/spacing_micro" + android:src="@drawable/ic_overflow"/> - + + - - + + + android:layout_marginEnd="@dimen/spacing_xs_large" + android:layout_marginStart="@dimen/avatar_margin" + android:layout_marginTop="@dimen/spacing_micro" + android:gravity="start" + android:visibility="gone" + tools:ignore="RtlSymmetry" + tools:text="U+1F602" + tools:visibility="visible"/> - + - + + android:gravity="center"/> - \ No newline at end of file + \ 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 8ee75d10d..012dd54ce 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -589,26 +589,9 @@
• Why am I having problems editing Issues/PRs?

If you are editing a public Org repo, then please contact your Org to grant access to FastHub or use Access Token to login!.

-
• I\'m blocked from using FastHub, WHY?
-

Simply, because you have rated FastHub low and requested things via the play store and after I responded you did not update your review. If the app is no good, - why would you want to keep using it anyway?

-
• I\'m having this issue or I want this & that!!

Head to https://github.com/k0shk0sh/FastHub/issues/new and create new issue for bugs or feature requests, I really do encourage you to search before opening a ticket. Any duplicate request will result in it being closed immediately.

- - Message from the developer of FastHub,\n - -

Hello Dear Developers,

\n -

- As you might know FastHub is an open source App & has grow so fast and that\'s because of the help from the community and you - guys.\nReviewing FastHub low in the PlayStore because you want to report an issue or request any feature will result to it being - ignored.\nAs developers I think we understand how much effort does it take to make an App like FastHub.\nSome don\'t get that at all - and instead of reporting issues/features in FastHub\'s repo they rate FastHub low in the PlayStore thinking that this will force - the developer (ME) to implement and fix their stuff so they update their review, but this doesn\'t happen, instead they either never - update their review or they\'ll request something else (so typical).\nPlease report Issues & Feature requests in the FastHub repo, open - up the drawer menu & click on the Report Issue & it\'ll be posted directly to FastHub repo where I can help/assist you. -

]]>
FAQ Comments added successfully diff --git a/build.gradle b/build.gradle index 0b0e929fe..831bd8c31 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { state_version = '1.1.0' lombokVersion = '1.12.6' supportVersion = "26.0.1" - gms = "11.2.0" + gms = "11.4.0" thirtyinchVersion = '0.8.0' retrofit = '2.3.0' junitVersion = '4.12' @@ -13,7 +13,7 @@ buildscript { assertjVersion = '2.5.0' espresseVersion = '2.2.2' requery = '1.3.2' - kotlin_version = '1.1.4-2' + kotlin_version = '1.1.4-3' commonmark = '0.9.0' } repositories { @@ -22,7 +22,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-beta5' + classpath 'com.android.tools.build:gradle:3.0.0-beta6' classpath 'com.google.gms:google-services:3.0.0' classpath 'com.novoda:gradle-build-properties-plugin:0.3' classpath 'com.dicedmelon.gradle:jacoco-android:0.1.2' From e6230fe91f98f55db5e99bcc6e1dcce635d971d4 Mon Sep 17 00:00:00 2001 From: k0shk0sh Date: Sun, 1 Oct 2017 10:30:21 +0200 Subject: [PATCH 15/36] releasing 4.4.0 --- .gitignore | 3 +- app/build.gradle | 6 +- .../timeline/handler/ListsHandler.java | 4 +- .../timeline/handler/MarginHandler.java | 2 - .../modules/main/premium/PremiumActivity.kt | 5 +- .../modules/main/premium/PremiumPresenter.kt | 13 +- app/src/main/res/raw/changelog.html | 26 +- app/src/main/res/values/strings.xml | 13 + jobdispatcher/build.gradle | 79 ---- jobdispatcher/coverage.gradle | 40 -- .../src/androidTest/AndroidManifest.xml | 35 -- .../firebase/jobdispatcher/EndToEndTest.java | 69 ---- jobdispatcher/src/main/AndroidManifest.xml | 32 -- .../jobdispatcher/BundleProtocol.java | 49 --- .../firebase/jobdispatcher/Constraint.java | 108 ----- .../jobdispatcher/DefaultJobValidator.java | 288 ------------- .../com/firebase/jobdispatcher/Driver.java | 62 --- .../jobdispatcher/ExecutionDelegator.java | 160 -------- .../jobdispatcher/FirebaseJobDispatcher.java | 211 ---------- .../GooglePlayCallbackExtractor.java | 247 ------------ .../jobdispatcher/GooglePlayDriver.java | 162 -------- .../jobdispatcher/GooglePlayJobCallback.java | 56 --- .../jobdispatcher/GooglePlayJobWriter.java | 198 --------- .../GooglePlayMessageHandler.java | 114 ------ .../GooglePlayMessengerCallback.java | 61 --- .../jobdispatcher/GooglePlayReceiver.java | 274 ------------- .../java/com/firebase/jobdispatcher/Job.java | 378 ------------------ .../firebase/jobdispatcher/JobCallback.java | 28 -- .../com/firebase/jobdispatcher/JobCoder.java | 253 ------------ .../firebase/jobdispatcher/JobInvocation.java | 236 ----------- .../firebase/jobdispatcher/JobParameters.java | 86 ---- .../firebase/jobdispatcher/JobService.java | 259 ------------ .../jobdispatcher/JobServiceConnection.java | 82 ---- .../firebase/jobdispatcher/JobTrigger.java | 70 ---- .../firebase/jobdispatcher/JobValidator.java | 49 --- .../com/firebase/jobdispatcher/Lifetime.java | 40 -- .../firebase/jobdispatcher/ObservedUri.java | 83 ---- .../firebase/jobdispatcher/RetryStrategy.java | 106 ----- .../jobdispatcher/SimpleJobService.java | 90 ----- .../com/firebase/jobdispatcher/Trigger.java | 73 ---- .../firebase/jobdispatcher/TriggerReason.java | 33 -- .../jobdispatcher/ValidationEnforcer.java | 132 ------ .../android/net/http/AndroidHttpClient.java | 10 - .../jobdispatcher/ConstraintTest.java | 66 --- .../jobdispatcher/ContentUriTriggerTest.java | 52 --- .../DefaultJobValidatorTest.java | 119 ------ .../jobdispatcher/ExecutionDelegatorTest.java | 224 ----------- .../ExecutionWindowTriggerTest.java | 76 ---- .../jobdispatcher/ExtendedShadowParcel.java | 40 -- .../FirebaseJobDispatcherTest.java | 205 ---------- .../GooglePlayCallbackExtractorTest.java | 153 ------- .../jobdispatcher/GooglePlayDriverTest.java | 203 ---------- .../GooglePlayJobWriterTest.java | 268 ------------- .../GooglePlayMessageHandlerTest.java | 153 ------- .../GooglePlayMessengerCallbackTest.java | 63 --- .../jobdispatcher/GooglePlayReceiverTest.java | 327 --------------- .../jobdispatcher/ImmediateTriggerTest.java | 34 -- .../jobdispatcher/JobBuilderTest.java | 65 --- .../firebase/jobdispatcher/JobCoderTest.java | 158 -------- .../jobdispatcher/JobInvocationTest.java | 83 ---- .../JobServiceConnectionTest.java | 116 ------ .../jobdispatcher/JobServiceTest.java | 346 ---------------- .../jobdispatcher/ValidationEnforcerTest.java | 187 --------- .../jobdispatcher/NoopJobValidator.java | 44 -- .../jobdispatcher/TestJobService.java | 71 ---- .../com/firebase/jobdispatcher/TestUtil.java | 375 ----------------- .../android/gms/gcm/PendingCallback.java | 61 --- settings.gradle | 2 +- 68 files changed, 42 insertions(+), 7774 deletions(-) delete mode 100644 jobdispatcher/build.gradle delete mode 100644 jobdispatcher/coverage.gradle delete mode 100644 jobdispatcher/src/androidTest/AndroidManifest.xml delete mode 100644 jobdispatcher/src/androidTest/java/com/firebase/jobdispatcher/EndToEndTest.java delete mode 100644 jobdispatcher/src/main/AndroidManifest.xml delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/BundleProtocol.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/Constraint.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/DefaultJobValidator.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/Driver.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/ExecutionDelegator.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/FirebaseJobDispatcher.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayCallbackExtractor.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayDriver.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayJobCallback.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayJobWriter.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayMessageHandler.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayMessengerCallback.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayReceiver.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/Job.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobCallback.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobCoder.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobInvocation.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobParameters.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobService.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobServiceConnection.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobTrigger.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobValidator.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/Lifetime.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/ObservedUri.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/RetryStrategy.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/SimpleJobService.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/Trigger.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/TriggerReason.java delete mode 100644 jobdispatcher/src/main/java/com/firebase/jobdispatcher/ValidationEnforcer.java delete mode 100644 jobdispatcher/src/test/java/android/net/http/AndroidHttpClient.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/ConstraintTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/ContentUriTriggerTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/DefaultJobValidatorTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/ExecutionDelegatorTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/ExecutionWindowTriggerTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/ExtendedShadowParcel.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/FirebaseJobDispatcherTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayCallbackExtractorTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayDriverTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayJobWriterTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayMessageHandlerTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayMessengerCallbackTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayReceiverTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/ImmediateTriggerTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobBuilderTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobCoderTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobInvocationTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobServiceConnectionTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobServiceTest.java delete mode 100644 jobdispatcher/src/test/java/com/firebase/jobdispatcher/ValidationEnforcerTest.java delete mode 100644 jobdispatcher/src/testLib/java/com/firebase/jobdispatcher/NoopJobValidator.java delete mode 100644 jobdispatcher/src/testLib/java/com/firebase/jobdispatcher/TestJobService.java delete mode 100644 jobdispatcher/src/testLib/java/com/firebase/jobdispatcher/TestUtil.java delete mode 100644 jobdispatcher/src/testLib/java/com/google/android/gms/gcm/PendingCallback.java diff --git a/.gitignore b/.gitignore index 3133b75a7..0c77a1244 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,4 @@ /app/google-services.json /app/build/ /app/src/main/res/values/secrets.xml -/app/fastaccess-key -/jobdispatcher/build/ +/app/fastaccess-key \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e964c6397..0b373ce27 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,8 +29,8 @@ android { applicationId "com.fastaccess.github" minSdkVersion 21 targetSdkVersion 26 - versionCode 430 - versionName "4.3.0" + versionCode 440 + versionName "4.4.0" buildConfigString "GITHUB_CLIENT_ID", (buildProperties.secrets['github_client_id'] | buildProperties.notThere['github_client_id']).string buildConfigString "GITHUB_SECRET", (buildProperties.secrets['github_secret'] | buildProperties.notThere['github_secret']).string buildConfigString "IMGUR_CLIENT_ID", (buildProperties.secrets['imgur_client_id'] | buildProperties.notThere['imgur_client_id']).string @@ -167,7 +167,7 @@ dependencies { implementation 'com.jaredrummler:android-device-names:1.1.4' implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:2.1.0' implementation 'com.airbnb.android:lottie:2.2.0' - implementation project(path: ':jobdispatcher') + implementation 'com.firebase:firebase-jobdispatcher:0.8.2' compileOnly "org.projectlombok:lombok:${lombokVersion}" kapt "org.projectlombok:lombok:${lombokVersion}" kapt "com.evernote:android-state-processor:${state_version}" diff --git a/app/src/main/java/com/fastaccess/provider/timeline/handler/ListsHandler.java b/app/src/main/java/com/fastaccess/provider/timeline/handler/ListsHandler.java index 28f5641a6..6e5d01de3 100644 --- a/app/src/main/java/com/fastaccess/provider/timeline/handler/ListsHandler.java +++ b/app/src/main/java/com/fastaccess/provider/timeline/handler/ListsHandler.java @@ -4,6 +4,7 @@ import android.support.annotation.Nullable; import android.text.SpannableStringBuilder; +import com.fastaccess.helper.Logger; import com.fastaccess.ui.widgets.SpannableBuilder; import net.nightwhistler.htmlspanner.TagNodeHandler; @@ -42,10 +43,11 @@ private String getParentName(TagNode node) { return node.getParent() == null ? null : node.getParent().getName(); } - @Override public void beforeChildren(TagNode node, SpannableStringBuilder builder) { + @Override public void beforeChildren(TagNode node, SpannableStringBuilder builder) { TodoItems todoItem = null; if (node.getChildTags() != null && node.getChildTags().length > 0) { for (TagNode tagNode : node.getChildTags()) { + Logger.e(tagNode.getName(), tagNode.getText()); if (tagNode.getName() != null && tagNode.getName().equals("input")) { todoItem = new TodoItems(); todoItem.isChecked = tagNode.getAttributeByName("checked") != null; diff --git a/app/src/main/java/com/fastaccess/provider/timeline/handler/MarginHandler.java b/app/src/main/java/com/fastaccess/provider/timeline/handler/MarginHandler.java index f5435a086..3fec4aa40 100644 --- a/app/src/main/java/com/fastaccess/provider/timeline/handler/MarginHandler.java +++ b/app/src/main/java/com/fastaccess/provider/timeline/handler/MarginHandler.java @@ -20,10 +20,8 @@ public void beforeChildren(TagNode node, SpannableStringBuilder builder) { } } - public void handleTagNode(TagNode node, SpannableStringBuilder builder, int start, int end) { builder.setSpan(new LeadingMarginSpan.Standard(30), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); this.appendNewLine(builder); - this.appendNewLine(builder); } } diff --git a/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumActivity.kt b/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumActivity.kt index 86cee4f25..9801b3141 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumActivity.kt +++ b/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumActivity.kt @@ -16,7 +16,6 @@ import com.fastaccess.BuildConfig import com.fastaccess.R import com.fastaccess.helper.AppHelper import com.fastaccess.helper.InputHelper -import com.fastaccess.helper.PrefGetter import com.fastaccess.helper.ViewHelper import com.fastaccess.provider.fabric.FabricProvider import com.fastaccess.ui.base.BaseActivity @@ -76,7 +75,7 @@ class PremiumActivity : BaseActivity(), Premi return true } - @OnClick(R.id.close) fun onClose(): Unit = finish() + @OnClick(R.id.close) fun onClose() = finish() override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) @@ -97,8 +96,6 @@ class PremiumActivity : BaseActivity(), Premi override fun onAnimationRepeat(p0: Animator?) {} override fun onAnimationEnd(p0: Animator?) { FabricProvider.logPurchase(InputHelper.toString(editText)) - PrefGetter.setProItems() - PrefGetter.setEnterpriseItem() showMessage(R.string.success, R.string.success) successResult() } diff --git a/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumPresenter.kt b/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumPresenter.kt index ab1cb8f8f..ac73d355c 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumPresenter.kt +++ b/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumPresenter.kt @@ -1,6 +1,6 @@ package com.fastaccess.ui.modules.main.premium -import com.fastaccess.helper.Logger +import com.fastaccess.helper.PrefGetter import com.fastaccess.helper.RxHelper import com.fastaccess.ui.base.mvp.presenter.BasePresenter import com.github.b3er.rxfirebase.database.data @@ -26,13 +26,20 @@ class PremiumPresenter : BasePresenter(), PremiumMvp.Presenter val map = it.getValue(gti) exists = map?.contains(promo) } - Logger.e(it.children, it.childrenCount, exists) return@flatMap Observable.just(exists) } .doOnComplete { sendToView { it.hideProgress() } } .subscribe({ when (it) { - true -> sendToView { it.onSuccessfullyActivated() } + true -> sendToView { + if (promo.contains("student")) { + PrefGetter.setProItems() + } else { + PrefGetter.setProItems() + PrefGetter.setEnterpriseItem() + } + it.onSuccessfullyActivated() + } else -> sendToView { it.onNoMatch() } } }, ::println)) diff --git a/app/src/main/res/raw/changelog.html b/app/src/main/res/raw/changelog.html index 5bf6dd1f2..14695aae0 100644 --- a/app/src/main/res/raw/changelog.html +++ b/app/src/main/res/raw/changelog.html @@ -8,28 +8,22 @@

FastHub changelog

-

Version 4.3.0 (Project Columns and Cards) +

Version 4.4.0 (Org Project Columns and Cards)

-

Reporting Issues or Feature Requests in Google Play review section, will be ignored or might even get your account to be blocked from - FastHub. You are using an app for GitHub, which provides a proper way to report issues. -
- Please report the issues in FastHub repo instead, by opening the Drawer Menu and clicking on “Report an Issue” - PLEASE USE IT. +

Please report the issues in FastHub repo instead, by opening the Drawer Menu and clicking on “Report an Issue” + PLEASE USE IT.

-

Bugs , Enhancements & new Features (4.3.0) +

Bugs , Enhancements & new Features (4.4.0)

    -
  • Project Columns & Cards (Edit, Create & Delete)
  • -
  • (New) Repo Collaborators now can Edit, delete & create files.
  • -
  • (New) Repo Collaborators now can Edit, delete & Comments.
  • -
  • (New) Long press in Feeds to navigate directly to Repo.
  • -
  • (Enhancement) Made Search to have minimum 2 chars.
  • -
  • (Enhancement) Adding blockable progress when adding new comment.
  • -
  • (Fix) Crash in PRs & Issues comments due to Table rendering!.
  • -
  • (Fix) ReadMe scrolling when Device Animation is turned off.
  • -
  • (Fix) Closed/Opened Issue pagination where most of the issues in the page are PRs (GitHub API!!!!).
  • +
  • (New) Org Project Columns & Cards (Edit, Create & Delete)
  • +
  • (New) Displaying Labels under Issue/Pr description.
  • +
  • (Enhancement) Removal of PR review limit.
  • +
  • (Enhancement) Selected text will be taken in consideration when adding Image/Link.
  • +
  • (Enhancement) Removed Loading background.
  • +
  • (Enhancement) More markdown enhancement.
  • (Fix) Lots of bug fixes.
  • There are more stuff are not mentioned, find them out :stuck_out_tongue:
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 012dd54ce..1a5c453ff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -592,6 +592,19 @@
• I\'m having this issue or I want this & that!!

Head to https://github.com/k0shk0sh/FastHub/issues/new and create new issue for bugs or feature requests, I really do encourage you to search before opening a ticket. Any duplicate request will result in it being closed immediately.

+ +
• How do I get PROMO CODE?
+

If you are a student, you\'ll have to provide me via Email that you are student, you will need below documents:

+
    +
  • Your university identity card & your identity card (that shows your name & your face to compare it!)
  • +
  • Your university start & end date
  • +
  • Rate FastHub in the Play Store
  • +
+

If you aren\'t a student and you can\'t afford to pay for PRO, you\'ll need:

+
    +
  • Write an article about FastHub in social media such as (Medium)
  • +
  • Rate FastHub in the Play Store
  • +
]]> FAQ Comments added successfully diff --git a/jobdispatcher/build.gradle b/jobdispatcher/build.gradle deleted file mode 100644 index d326ddf27..000000000 --- a/jobdispatcher/build.gradle +++ /dev/null @@ -1,79 +0,0 @@ -apply plugin: "com.android.library" - -android { - compileSdkVersion 26 - buildToolsVersion "26.0.1" - - defaultConfig { - minSdkVersion 16 - targetSdkVersion 26 - versionCode 1 - versionName "0.8.0" - testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' - } - - defaultPublishConfig "release" - publishNonDefault true - - buildTypes { - debug { - testCoverageEnabled true - } - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt') - } - } - - sourceSets { - // A set of testing helpers that are shared across test types - testLib { java.srcDir("src/main") } - test { java.srcDir("src/testLib") } // Robolectric tests - androidTest { java.srcDir("src/testLib") } // Android (e2e) tests - } -} - -dependencies { - // The main library only depends on the Android support lib - compile "com.android.support:support-v4:26.0.1" - - def junit = 'junit:junit:4.12' - def robolectric = 'org.robolectric:robolectric:3.3.2' - - // The common test library uses JUnit - testLibCompile junit - - // The unit tests are written using JUnit, Robolectric, and Mockito - testCompile junit - testCompile robolectric - testCompile 'org.mockito:mockito-core:2.2.5' - - // The Android (e2e) tests are written using JUnit and the test support lib - androidTestCompile junit - androidTestCompile 'com.android.support.test:runner:0.5' -} - -task javadocs(type: Javadoc) { - description "Generate Javadocs" - source = android.sourceSets.main.java.sourceFiles - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) - classpath += configurations.compile - failOnError false -} - -task javadocsJar(type: Jar, dependsOn: javadocs) { - description "Package Javadocs into a jar" - classifier = "javadoc" - from javadocs.destinationDir -} - -task sourcesJar(type: Jar) { - description "Package sources into a jar" - classifier = "sources" - from android.sourceSets.main.java.sourceFiles -} - -task aar(dependsOn: "assembleRelease") { - group "artifact" - description "Builds the library AARs" -} diff --git a/jobdispatcher/coverage.gradle b/jobdispatcher/coverage.gradle deleted file mode 100644 index 204f2dc22..000000000 --- a/jobdispatcher/coverage.gradle +++ /dev/null @@ -1,40 +0,0 @@ -apply plugin: "jacoco" - -jacoco { - // see https://github.com/jacoco/jacoco/pull/288 and the top build.gradle - toolVersion "0.7.6.201602180812" -} - -android { - testOptions { - unitTests.all { - systemProperty "robolectric.logging.enabled", true - systemProperty "robolectric.logging", "stdout" - - jacoco { - includeNoLocationClasses = true - } - } - } -} - -// ignore these when generating coverage -def ignoredPrefixes = ['R$', 'R.class', 'BuildConfig.class'] - -task coverage(type: JacocoReport, dependsOn: ["testDebugUnitTest"]) { - group = "Reports" - description = "Generate a coverage report" - - classDirectories = fileTree( - dir: "${project.buildDir}/intermediates/classes/debug/com/firebase/", - exclude: { d -> ignoredPrefixes.any { p -> d.file.name.startsWith(p) } } - ) - sourceDirectories = files(["src/main/java/com/firebase/"]) - executionData = files("${project.buildDir}/jacoco/testDebugUnitTest.exec") - - reports { - xml.enabled = true - html.enabled = true - html.destination "${buildDir}/coverage_html" - } -} diff --git a/jobdispatcher/src/androidTest/AndroidManifest.xml b/jobdispatcher/src/androidTest/AndroidManifest.xml deleted file mode 100644 index c87f27fcf..000000000 --- a/jobdispatcher/src/androidTest/AndroidManifest.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/jobdispatcher/src/androidTest/java/com/firebase/jobdispatcher/EndToEndTest.java b/jobdispatcher/src/androidTest/java/com/firebase/jobdispatcher/EndToEndTest.java deleted file mode 100644 index 48b1048bc..000000000 --- a/jobdispatcher/src/androidTest/java/com/firebase/jobdispatcher/EndToEndTest.java +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2017 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static org.junit.Assert.assertTrue; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Basic end to end test for the JobDispatcher. Requires Google Play services be installed and - * available. - */ -@RunWith(AndroidJUnit4.class) -public final class EndToEndTest { - private Context appContext; - private FirebaseJobDispatcher dispatcher; - - @Before public void setUp() { - appContext = InstrumentationRegistry.getTargetContext(); - dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(appContext)); - TestJobService.reset(); - } - - @Test public void basicImmediateJob() throws InterruptedException { - final CountDownLatch latch = new CountDownLatch(1); - TestJobService.setProxy(new TestJobService.JobServiceProxy() { - @Override - public boolean onStartJob(JobParameters params) { - latch.countDown(); - return false; - } - - @Override - public boolean onStopJob(JobParameters params) { - return false; - } - }); - - dispatcher.mustSchedule( - dispatcher.newJobBuilder() - .setService(TestJobService.class) - .setTrigger(Trigger.NOW) - .setTag("basic-immediate-job") - .build()); - - assertTrue("Latch wasn't counted down as expected", latch.await(120, TimeUnit.SECONDS)); - } -} diff --git a/jobdispatcher/src/main/AndroidManifest.xml b/jobdispatcher/src/main/AndroidManifest.xml deleted file mode 100644 index 71205bd86..000000000 --- a/jobdispatcher/src/main/AndroidManifest.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/BundleProtocol.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/BundleProtocol.java deleted file mode 100644 index 774a83401..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/BundleProtocol.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -final class BundleProtocol { - static final String PACKED_PARAM_BUNDLE_PREFIX = "com.firebase.jobdispatcher."; - - // PACKED_PARAM values are only read on the client side, so as long as the - // extraction process gets the same changes then it's fine. - static final String PACKED_PARAM_CONSTRAINTS = "constraints"; - static final String PACKED_PARAM_LIFETIME = "persistent"; - static final String PACKED_PARAM_RECURRING = "recurring"; - static final String PACKED_PARAM_SERVICE = "service"; - static final String PACKED_PARAM_TAG = "tag"; - static final String PACKED_PARAM_EXTRAS = "extras"; - static final String PACKED_PARAM_TRIGGER_TYPE = "trigger_type"; - static final String PACKED_PARAM_TRIGGER_WINDOW_END = "window_end"; - static final String PACKED_PARAM_TRIGGER_WINDOW_START = "window_start"; - static final int TRIGGER_TYPE_EXECUTION_WINDOW = 1; - static final int TRIGGER_TYPE_IMMEDIATE = 2; - static final int TRIGGER_TYPE_CONTENT_URI = 3; - static final String PACKED_PARAM_RETRY_STRATEGY_INITIAL_BACKOFF_SECONDS = - "initial_backoff_seconds"; - static final String PACKED_PARAM_RETRY_STRATEGY_MAXIMUM_BACKOFF_SECONDS = - "maximum_backoff_seconds"; - static final String PACKED_PARAM_RETRY_STRATEGY_POLICY = "retry_policy"; - static final String PACKED_PARAM_REPLACE_CURRENT = "replace_current"; - static final String PACKED_PARAM_CONTENT_URI_FLAGS_ARRAY = "content_uri_flags_array"; - static final String PACKED_PARAM_CONTENT_URI_ARRAY = "content_uri_array"; - static final String PACKED_PARAM_TRIGGERED_URIS = "triggered_uris"; - static final String PACKED_PARAM_OBSERVED_URI = "observed_uris"; - - BundleProtocol() { - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Constraint.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Constraint.java deleted file mode 100644 index ff413fb20..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Constraint.java +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.support.annotation.IntDef; -import android.support.annotation.VisibleForTesting; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * A Constraint is a runtime requirement for a job. A job only becomes eligible to run once its - * trigger has been activated and all constraints are satisfied. - */ -public final class Constraint { - /** - * Only run the job when an unmetered network is available. - */ - public static final int ON_UNMETERED_NETWORK = 1; - - /** - * Only run the job when a network connection is available. If both this and - * {@link #ON_UNMETERED_NETWORK} is provided, {@link #ON_UNMETERED_NETWORK} will take - * precedence. - */ - public static final int ON_ANY_NETWORK = 1 << 1; - - /** - * Only run the job when the device is currently charging. - */ - public static final int DEVICE_CHARGING = 1 << 2; - - /** - * Only run the job when the device is idle. This is ignored for devices that don't expose the - * concept of an idle state. - */ - public static final int DEVICE_IDLE = 1 << 3; - - @VisibleForTesting - static final int[] ALL_CONSTRAINTS = { - ON_ANY_NETWORK, ON_UNMETERED_NETWORK, DEVICE_CHARGING, DEVICE_IDLE}; - - /** Constraint shouldn't ever be instantiated. */ - private Constraint() {} - - /** - * A tooling type-hint for any of the valid constraint values. - */ - @IntDef(flag = true, value = { - ON_ANY_NETWORK, - ON_UNMETERED_NETWORK, - DEVICE_CHARGING, - DEVICE_IDLE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface JobConstraint {} - - /** - * Compact a provided array of constraints into a single int. - * - * @see #uncompact(int) - */ - static int compact(@JobConstraint int[] constraints) { - int result = 0; - if (constraints == null) { - return result; - } - for (int c : constraints) { - result |= c; - } - return result; - } - - /** - * Unpack a single int into an array of constraints. - * - * @see #compact(int[]) - */ - static int[] uncompact(int compactConstraints) { - int length = 0; - for (int c : ALL_CONSTRAINTS) { - length += (compactConstraints & c) == c ? 1 : 0; - } - int[] list = new int[length]; - - int i = 0; - for (int c : ALL_CONSTRAINTS) { - if ((compactConstraints & c) == c) { - list[i++] = c; - } - } - - return list; - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/DefaultJobValidator.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/DefaultJobValidator.java deleted file mode 100644 index 4804804e5..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/DefaultJobValidator.java +++ /dev/null @@ -1,288 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static com.firebase.jobdispatcher.RetryStrategy.RETRY_POLICY_EXPONENTIAL; -import static com.firebase.jobdispatcher.RetryStrategy.RETRY_POLICY_LINEAR; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.Bundle; -import android.os.Parcel; -import android.support.annotation.CallSuper; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -/** - * Validates Jobs according to some safe standards. - *

- * Custom JobValidators should typically extend from this. - */ -public class DefaultJobValidator implements JobValidator { - - /** - * The maximum length of a tag, in characters (i.e. String.length()). Strings longer than this - * will cause validation to fail. - */ - public static final int MAX_TAG_LENGTH = 100; - - /** - * The maximum size, in bytes, that the provided extras bundle can be. Corresponds to - * {@link Parcel#dataSize()}. - */ - public final static int MAX_EXTRAS_SIZE_BYTES = 10 * 1024; - - /** Private ref to the Context. Necessary to check that the manifest is configured correctly. */ - private final Context context; - - public DefaultJobValidator(Context context) { - this.context = context; - } - - /** @see {@link #MAX_EXTRAS_SIZE_BYTES}. */ - private static int measureBundleSize(Bundle extras) { - Parcel p = Parcel.obtain(); - extras.writeToParcel(p, 0); - int sizeInBytes = p.dataSize(); - p.recycle(); - - return sizeInBytes; - } - - /** Combines two {@literal Lists} together. */ - @Nullable - private static List mergeErrorLists(@Nullable List errors, - @Nullable List newErrors) { - if (errors == null) { - return newErrors; - } - if (newErrors == null) { - return errors; - } - - errors.addAll(newErrors); - return errors; - } - - @Nullable - private static List addError(@Nullable List errors, String newError) { - if (newError == null) { - return errors; - } - if (errors == null) { - return getMutableSingletonList(newError); - } - - Collections.addAll(errors, newError); - - return errors; - } - - @Nullable - private static List addErrorsIf(boolean condition, List errors, String newErr) { - if (condition) { - return addError(errors, newErr); - } - - return errors; - } - - /** - * Attempts to validate the provided {@code JobParameters}. If the JobParameters is valid, null will be - * returned. If the JobParameters has errors, a list of those errors will be returned. - */ - @Nullable - @Override - @CallSuper - public List validate(JobParameters job) { - List errors = null; - - errors = mergeErrorLists(errors, validate(job.getTrigger())); - errors = mergeErrorLists(errors, validate(job.getRetryStrategy())); - - if (job.isRecurring() && job.getTrigger() == Trigger.NOW) { - errors = addError(errors, "ImmediateTriggers can't be used with recurring jobs"); - } - - errors = mergeErrorLists(errors, validateForTransport(job.getExtras())); - if (job.getLifetime() > Lifetime.UNTIL_NEXT_BOOT) { - //noinspection ConstantConditions - errors = mergeErrorLists(errors, validateForPersistence(job.getExtras())); - } - - errors = mergeErrorLists(errors, validateTag(job.getTag())); - errors = mergeErrorLists(errors, validateService(job.getService())); - - return errors; - } - - /** - * Attempts to validate the provided Trigger. If valid, null is returned. Otherwise a list of - * errors will be returned. - *

- * Note that a Trigger that passes validation here is not necessarily valid in all permutations - * of a JobParameters. For example, an Immediate is never valid for a recurring job. - * @param trigger - */ - @Nullable - @Override - @CallSuper - public List validate(JobTrigger trigger) { - if (trigger != Trigger.NOW - && !(trigger instanceof JobTrigger.ExecutionWindowTrigger) - && !(trigger instanceof JobTrigger.ContentUriTrigger)) { - return getMutableSingletonList("Unknown trigger provided"); - } - - return null; - } - - /** - * Attempts to validate the provided RetryStrategy. If valid, null is returned. Otherwise a list - * of errors will be returned. - */ - @Nullable - @Override - @CallSuper - public List validate(RetryStrategy retryStrategy) { - List errors = null; - - int policy = retryStrategy.getPolicy(); - int initial = retryStrategy.getInitialBackoff(); - int maximum = retryStrategy.getMaximumBackoff(); - - errors = addErrorsIf(policy != RETRY_POLICY_EXPONENTIAL && policy != RETRY_POLICY_LINEAR, - errors, "Unknown retry policy provided"); - errors = addErrorsIf(maximum < initial, - errors, "Maximum backoff must be greater than or equal to initial backoff"); - errors = addErrorsIf(300 > maximum, - errors, "Maximum backoff must be greater than 300s (5 minutes)"); - errors = addErrorsIf(initial < 30, - errors, "Initial backoff must be at least 30s"); - - return errors; - } - - @Nullable - private List validateForPersistence(Bundle extras) { - List errors = null; - - if (extras != null) { - // check the types to make sure they're persistable - for (String k : extras.keySet()) { - errors = addError(errors, validateExtrasType(extras, k)); - } - } - - return errors; - } - - @Nullable - private List validateForTransport(Bundle extras) { - if (extras == null) { - return null; - } - - int bundleSizeInBytes = measureBundleSize(extras); - if (bundleSizeInBytes > MAX_EXTRAS_SIZE_BYTES) { - return getMutableSingletonList(String.format(Locale.US, - "Extras too large: %d bytes is > the max (%d bytes)", - bundleSizeInBytes, MAX_EXTRAS_SIZE_BYTES)); - } - - return null; - } - - @Nullable - private String validateExtrasType(Bundle extras, String key) { - Object o = extras.get(key); - - if (o == null - || o instanceof Integer - || o instanceof Long - || o instanceof Double - || o instanceof String - || o instanceof Boolean) { - return null; - } - - return String.format(Locale.US, - "Received value of type '%s' for key '%s', but only the" - + " following extra parameter types are supported:" - + " Integer, Long, Double, String, and Boolean", - o == null ? null : o.getClass(), key); - } - - private List validateService(String service) { - if (service == null || service.isEmpty()) { - return getMutableSingletonList("Service can't be empty"); - } - - if (context == null) { - return getMutableSingletonList("Context is null, can't query PackageManager"); - } - - PackageManager pm = context.getPackageManager(); - if (pm == null) { - return getMutableSingletonList("PackageManager is null, can't validate service"); - } - - final String msg = "Couldn't find a registered service with the name " + service - + ". Is it declared in the manifest with the right intent-filter?"; - - Intent executeIntent = new Intent(JobService.ACTION_EXECUTE); - executeIntent.setClassName(context, service); - List intentServices = pm.queryIntentServices(executeIntent, 0); - if (intentServices == null || intentServices.isEmpty()) { - return getMutableSingletonList(msg); - } - - for (ResolveInfo info : intentServices) { - if (info.serviceInfo != null && info.serviceInfo.enabled) { - // found a match! - return null; - } - } - - return getMutableSingletonList(msg); - } - - private List validateTag(String tag) { - if (tag == null) { - return getMutableSingletonList("Tag can't be null"); - } - - if (tag.length() > MAX_TAG_LENGTH) { - return getMutableSingletonList("Tag must be shorter than " + MAX_TAG_LENGTH); - } - - return null; - } - - @NonNull - private static List getMutableSingletonList(String msg) { - ArrayList strings = new ArrayList<>(); - strings.add(msg); - return strings; - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Driver.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Driver.java deleted file mode 100644 index fe832721c..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Driver.java +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.support.annotation.NonNull; -import com.firebase.jobdispatcher.FirebaseJobDispatcher.CancelResult; -import com.firebase.jobdispatcher.FirebaseJobDispatcher.ScheduleResult; - -/** - * Driver represents a component that understands how to schedule, validate, and execute jobs. - */ -public interface Driver { - - /** - * Schedules the provided Job. - * - * @return one of the SCHEDULE_RESULT_ constants - */ - @ScheduleResult - int schedule(@NonNull Job job); - - /** - * Cancels the job with the provided tag and class. - * - * @return one of the CANCEL_RESULT_ constants. - */ - @CancelResult - int cancel(@NonNull String tag); - - /** - * Cancels all jobs registered with this Driver. - * - * @return one of the CANCEL_RESULT_ constants. - */ - @CancelResult - int cancelAll(); - - /** - * Returns a JobValidator configured for this backend. - */ - @NonNull - JobValidator getValidator(); - - /** - * Indicates whether the backend is available. - */ - boolean isAvailable(); -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/ExecutionDelegator.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/ExecutionDelegator.java deleted file mode 100644 index 1dbacb3cb..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/ExecutionDelegator.java +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static android.content.Context.BIND_AUTO_CREATE; - -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.support.annotation.NonNull; -import android.support.annotation.VisibleForTesting; -import android.support.v4.util.SimpleArrayMap; -import android.util.Log; -import com.firebase.jobdispatcher.JobService.JobResult; -import java.lang.ref.WeakReference; - -/** - * ExecutionDelegator tracks local Binder connections to client JobServices and handles - * communication with those services. - */ -/* package */ class ExecutionDelegator { - @VisibleForTesting - static final int JOB_FINISHED = 1; - - static final String TAG = "FJD.ExternalReceiver"; - - interface JobFinishedCallback { - void onJobFinished(@NonNull JobInvocation jobInvocation, @JobResult int result); - } - - /** - * A mapping of {@link JobInvocation} to (local) binder connections. - * Synchronized by itself. - */ - private final SimpleArrayMap serviceConnections = - new SimpleArrayMap<>(); - private final ResponseHandler responseHandler = - new ResponseHandler(Looper.getMainLooper(), new WeakReference<>(this)); - private final Context context; - private final JobFinishedCallback jobFinishedCallback; - - ExecutionDelegator(Context context, JobFinishedCallback jobFinishedCallback) { - this.context = context; - this.jobFinishedCallback = jobFinishedCallback; - } - - /** - * Executes the provided {@code jobInvocation} by kicking off the creation of a new Binder - * connection to the Service. - * - * @return true if the service was bound successfully. - */ - boolean executeJob(JobInvocation jobInvocation) { - if (jobInvocation == null) { - return false; - } - - JobServiceConnection conn = new JobServiceConnection(jobInvocation, - responseHandler.obtainMessage(JOB_FINISHED)); - - synchronized (serviceConnections) { - JobServiceConnection oldConnection = serviceConnections.put(jobInvocation, conn); - if (oldConnection != null) { - Log.e(TAG, "Received execution request for already running job"); - } - return context.bindService(createBindIntent(jobInvocation), conn, BIND_AUTO_CREATE); - } - } - - @NonNull - private Intent createBindIntent(JobParameters jobParameters) { - Intent execReq = new Intent(JobService.ACTION_EXECUTE); - execReq.setClassName(context, jobParameters.getService()); - return execReq; - } - - void stopJob(JobInvocation job) { - synchronized (serviceConnections) { - JobServiceConnection jobServiceConnection = serviceConnections.remove(job); - if (jobServiceConnection != null) { - jobServiceConnection.onStop(); - safeUnbindService(jobServiceConnection); - } - } - } - - private void safeUnbindService(JobServiceConnection connection) { - if (connection != null && connection.isBound()) { - try { - context.unbindService(connection); - } catch (IllegalArgumentException e) { - Log.w(TAG, "Error unbinding service: " + e.getMessage()); - } - } - } - - private void onJobFinishedMessage(JobInvocation jobInvocation, int result) { - synchronized (serviceConnections) { - JobServiceConnection connection = serviceConnections.remove(jobInvocation); - safeUnbindService(connection); - } - - jobFinishedCallback.onJobFinished(jobInvocation, result); - } - - private static class ResponseHandler extends Handler { - - /** - * We hold a WeakReference to the ExecutionDelegator because it holds a reference to a - * Service Context and Handlers are often kept in memory longer than you'd expect because - * any pending Messages can maintain references to them. - */ - private final WeakReference executionDelegatorReference; - - ResponseHandler(Looper looper, WeakReference executionDelegator) { - super(looper); - this.executionDelegatorReference = executionDelegator; - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case JOB_FINISHED: - if (msg.obj instanceof JobInvocation) { - ExecutionDelegator delegator = this.executionDelegatorReference.get(); - if (delegator == null) { - Log.wtf(TAG, "handleMessage: service was unexpectedly GC'd" - + ", can't send job result"); - return; - } - - delegator.onJobFinishedMessage((JobInvocation) msg.obj, msg.arg1); - return; - } - - Log.wtf(TAG, "handleMessage: unknown obj returned"); - return; - - default: - Log.wtf(TAG, "handleMessage: unknown message type received: " + msg.what); - } - } - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/FirebaseJobDispatcher.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/FirebaseJobDispatcher.java deleted file mode 100644 index 1a76093f8..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/FirebaseJobDispatcher.java +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import com.firebase.jobdispatcher.RetryStrategy.RetryPolicy; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * The FirebaseJobDispatcher provides a driver-agnostic API for scheduling and cancelling Jobs. - * - * @see #FirebaseJobDispatcher(Driver) - * @see Driver - * @see JobParameters - */ -public final class FirebaseJobDispatcher { - /** - * Indicates the schedule request seems to have been successful. - */ - public final static int SCHEDULE_RESULT_SUCCESS = 0; - - /** - * Indicates the schedule request encountered an unknown error. - */ - public final static int SCHEDULE_RESULT_UNKNOWN_ERROR = 1; - - /** - * Indicates the schedule request failed because the driver was unavailable. - */ - public final static int SCHEDULE_RESULT_NO_DRIVER_AVAILABLE = 2; - - /** - * Indicates the schedule request failed because the Trigger was unsupported. - */ - public final static int SCHEDULE_RESULT_UNSUPPORTED_TRIGGER = 3; - - /** - * Indicates the schedule request failed because the service is not exposed or configured - * correctly. - */ - public final static int SCHEDULE_RESULT_BAD_SERVICE = 4; - - /** - * Indicates the cancel request seems to have been successful. - */ - public final static int CANCEL_RESULT_SUCCESS = 0; - /** - * Indicates the cancel request encountered an unknown error. - */ - public final static int CANCEL_RESULT_UNKNOWN_ERROR = 1; - /** - * Indicates the cancel request failed because the driver was unavailable. - */ - public final static int CANCEL_RESULT_NO_DRIVER_AVAILABLE = 2; - /** - * The backing Driver for this instance. - */ - private final Driver mDriver; - /** - * The ValidationEnforcer configured for the current Driver. - */ - private final ValidationEnforcer mValidator; - /** - * Single instance of a RetryStrategy.Builder, configured with the current driver's validation - * settings. We can do this because the RetryStrategy.Builder is stateless. - */ - private RetryStrategy.Builder mRetryStrategyBuilder; - - /** - * Instantiates a new FirebaseJobDispatcher using the provided Driver. - */ - public FirebaseJobDispatcher(Driver driver) { - mDriver = driver; - mValidator = new ValidationEnforcer(mDriver.getValidator()); - mRetryStrategyBuilder = new RetryStrategy.Builder(mValidator); - } - - /** - * Attempts to schedule the provided Job. - *

- * Returns one of the SCHEDULE_RESULT_ constants. - */ - @ScheduleResult - public int schedule(@NonNull Job job) { - if (!mDriver.isAvailable()) { - return SCHEDULE_RESULT_NO_DRIVER_AVAILABLE; - } - - return mDriver.schedule(job); - } - - /** - * Attempts to cancel the Job that matches the provided tag and endpoint. - *

- * Returns one of the CANCEL_RESULT_ constants. - */ - @CancelResult - public int cancel(@NonNull String tag) { - if (!mDriver.isAvailable()) { - return CANCEL_RESULT_NO_DRIVER_AVAILABLE; - } - - return mDriver.cancel(tag); - } - - /** - * Attempts to cancel all Jobs registered for this package. - *

- * Returns one of the CANCEL_RESULT_ constants. - */ - @CancelResult - public int cancelAll() { - if (!mDriver.isAvailable()) { - return CANCEL_RESULT_NO_DRIVER_AVAILABLE; - } - - return mDriver.cancelAll(); - } - - /** - * Attempts to schedule the provided Job, throwing an exception if it fails. - * - * @throws ScheduleFailedException - */ - public void mustSchedule(Job job) { - if (schedule(job) != SCHEDULE_RESULT_SUCCESS) { - throw new ScheduleFailedException(); - } - } - - /** - * Returns a ValidationEnforcer configured for the current Driver. - */ - public ValidationEnforcer getValidator() { - return mValidator; - } - - /** - * Creates a new Job.Builder, configured with the current driver's validation settings. - */ - @NonNull - public Job.Builder newJobBuilder() { - return new Job.Builder(mValidator); - } - - /** - * Creates a new RetryStrategy from the provided parameters, validated with the current driver's - * {@link JobValidator}. - * - * @param policy the backoff policy to use. One of the {@link RetryPolicy} constants. - * @param initialBackoff the initial backoff, in seconds. - * @param maximumBackoff the maximum backoff, in seconds. - * @throws ValidationEnforcer.ValidationException - * @see RetryStrategy - */ - public RetryStrategy newRetryStrategy(@RetryPolicy int policy, int initialBackoff, - int maximumBackoff) { - - return mRetryStrategyBuilder.build(policy, initialBackoff, maximumBackoff); - } - - /** - * Results that can legally be returned from {@link #schedule(Job)} calls. - */ - @IntDef({ - SCHEDULE_RESULT_SUCCESS, - SCHEDULE_RESULT_UNKNOWN_ERROR, - SCHEDULE_RESULT_NO_DRIVER_AVAILABLE, - SCHEDULE_RESULT_UNSUPPORTED_TRIGGER, - SCHEDULE_RESULT_BAD_SERVICE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ScheduleResult { - } - - /** - * Results that can legally be returned from {@link #cancel(String)} or {@link #cancelAll()} - * calls. - */ - @IntDef({ - CANCEL_RESULT_SUCCESS, - CANCEL_RESULT_UNKNOWN_ERROR, - CANCEL_RESULT_NO_DRIVER_AVAILABLE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface CancelResult { - } - - /** - * Thrown when a {@link FirebaseJobDispatcher#schedule(com.firebase.jobdispatcher.Job)} call - * fails. - */ - public final static class ScheduleFailedException extends RuntimeException { - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayCallbackExtractor.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayCallbackExtractor.java deleted file mode 100644 index 03fca073d..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayCallbackExtractor.java +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.annotation.SuppressLint; -import android.os.Bundle; -import android.os.IBinder; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.Nullable; -import android.util.Log; -import android.util.Pair; -import java.util.ArrayList; - -/** - * Responsible for extracting a JobCallback from a given Bundle. - * - *

Google Play services will send the Binder packed inside a simple strong Binder wrapper ({@link - * #PENDING_CALLBACK_CLASS}) under the key {@link #BUNDLE_KEY_CALLBACK "callback"}. - */ -/* package */ final class GooglePlayCallbackExtractor { - - private static final String TAG = GooglePlayReceiver.TAG; - private static final String ERROR_NULL_CALLBACK = "No callback received, terminating"; - private static final String ERROR_INVALID_CALLBACK = "Bad callback received, terminating"; - - /** The Parcelable class that wraps the Binder we need to access. */ - private static final String PENDING_CALLBACK_CLASS = - "com.google.android.gms.gcm.PendingCallback"; - /** The key for the wrapped Binder. */ - private static final String BUNDLE_KEY_CALLBACK = "callback"; - /** A magic number that indicates the following bytes belong to a Bundle. */ - private static final int BUNDLE_MAGIC = 0x4C444E42; - /** A magic number that indicates the following value is a Parcelable. */ - private static final int VAL_PARCELABLE = 4; - - // GuardedBy("GooglePlayCallbackExtractor.class") - private static Boolean shouldReadKeysAsStringsCached = null; - - public Pair extractCallback(@Nullable Bundle data) { - if (data == null) { - Log.e(TAG, ERROR_NULL_CALLBACK); - return null; - } - - return extractWrappedBinderFromParcel(data); - } - - /** - * Bundles are written out in the following format: - * A header, which consists of: - *

    - *
  1. length (int)
  2. - *
  3. magic number ({@link #BUNDLE_MAGIC}) (int)
  4. - *
  5. number of entries (int)
  6. - *
- *

- * Then the map values, each of which looks like this: - *

    - *
  1. string key
  2. - *
  3. int type marker
  4. - *
  5. (any) parceled value
  6. - *
- *

- * We're just going to iterate over the map looking for the right key (BUNDLE_KEY_CALLBACK) - * and try and read the IBinder straight from the parcelled data. This is entirely dependent - * on the implementation of Parcel, but these specific parts of Parcel / Bundle haven't - * changed since 2008 and newer versions of Android will ship with newer versions of Google - * Play services which embed the IBinder directly into the Bundle (no need to deal with the - * Parcelable issues). - */ - @Nullable - @SuppressLint("ParcelClassLoader") - private Pair extractWrappedBinderFromParcel(Bundle data) { - Bundle cleanBundle = new Bundle(); - Parcel serialized = toParcel(data); - JobCallback callback = null; - - try { - int length = serialized.readInt(); - if (length <= 0) { - // Empty Bundle - Log.w(TAG, ERROR_NULL_CALLBACK); - return null; - } - - int magic = serialized.readInt(); - if (magic != BUNDLE_MAGIC) { - // Not a Bundle - Log.w(TAG, ERROR_NULL_CALLBACK); - return null; - } - - int numEntries = serialized.readInt(); - for (int i = 0; i < numEntries; i++) { - String entryKey = readKey(serialized); - if (entryKey == null) { - continue; - } - - if (!(callback == null && BUNDLE_KEY_CALLBACK.equals(entryKey))) { - // If it's not the 'callback' key, we can just read it using the standard - // mechanisms because we're not afraid of rogue BadParcelableExceptions. - Object value = serialized.readValue(null /* class loader */); - if (value instanceof String) { - cleanBundle.putString(entryKey, (String) value); - } else if (value instanceof Boolean) { - cleanBundle.putBoolean(entryKey, (boolean) value); - } else if (value instanceof Integer) { - cleanBundle.putInt(entryKey, (int) value); - } else if (value instanceof ArrayList) { - // The only acceptable ArrayList in a Bundle is one that consists entirely - // of Parcelables, so this cast is safe. - @SuppressWarnings("unchecked") // safe by specification - ArrayList arrayList = (ArrayList) value; - cleanBundle.putParcelableArrayList(entryKey, arrayList); - } else if (value instanceof Bundle) { - cleanBundle.putBundle(entryKey, (Bundle) value); - } else if (value instanceof Parcelable) { - cleanBundle.putParcelable(entryKey, (Parcelable) value); - } - - // Move to the next key - continue; - } - - int typeTag = serialized.readInt(); - if (typeTag != VAL_PARCELABLE) { - // If the key is correct ("callback"), but it's not a Parcelable then something - // went wrong and we should bail. - Log.w(TAG, ERROR_INVALID_CALLBACK); - return null; - } - - String clsname = serialized.readString(); - if (!PENDING_CALLBACK_CLASS.equals(clsname)) { - // If it's a Parcelable, but not one we recognize then we should not try and - // unpack it. - Log.w(TAG, ERROR_INVALID_CALLBACK); - return null; - } - - // Instead of trying to instantiate clsname, we'll just read its single member. - IBinder remote = serialized.readStrongBinder(); - callback = new GooglePlayJobCallback(remote); - } - - if (callback == null) { - Log.w(TAG, ERROR_NULL_CALLBACK); - return null; - } - return Pair.create(callback, cleanBundle); - } finally { - serialized.recycle(); - } - } - - private static Parcel toParcel(Bundle data) { - Parcel serialized = Parcel.obtain(); - data.writeToParcel(serialized, 0); - serialized.setDataPosition(0); - return serialized; - } - - /** - * Reads the next key (String) from the provided {@code serialized} Parcel. - * - *

Naively using {@link Parcel#readString()} fails on versions of Android older than L, - * whereas {@link Parcel#readValue(ClassLoader)} works on older versions but fails on anything L - * or newer. - */ - private String readKey(Parcel serialized) { - if (shouldReadKeysAsStrings()) { - return serialized.readString(); - } - - // Older platforms require readValue - Object entryKeyObj = serialized.readValue(null /* Use the system ClassLoader */); - if (!(entryKeyObj instanceof String)) { - // Should never happen (Bundle keys are always Strings) - Log.w(TAG, ERROR_INVALID_CALLBACK); - return null; - } - - return (String) entryKeyObj; - } - - /** - * Checks whether {@link Parcel#readString()} or {@link Parcel#readValue()} should be used to - * access Bundle keys from a serialized Parcel. Commit {@link - * https://android.googlesource.com/platform/frameworks/base/+/9c3e74f - * I57bda9eb79ceaaa9c1b94ad49d9e462b52102149} (which only officially landed in Lollipop) changed - * from using writeValue to writeString for Bundle keys. Some OEMs have pulled this change into - * their KitKat fork, so we can't trust the SDK version check. Instead, we'll write a dummy - * Bundle to a Parcel and figure it out using that. - * - * The check is cached because the result can't change during runtime. - */ - private static synchronized boolean shouldReadKeysAsStrings() { - // We're pretty sure that readString() should always be used on L+, but if we shortcircuit - // this check then we have no evidence that this code is functioning correctly on KitKat - // devices that have the corresponding writeString() change. - if (shouldReadKeysAsStringsCached == null) { - final String expectedKey = "key"; - Bundle testBundle = new Bundle(); - testBundle.putString(expectedKey, "value"); - Parcel testParcel = toParcel(testBundle); - try { - // length - checkCondition(testParcel.readInt() > 0); - // magic - checkCondition(testParcel.readInt() == BUNDLE_MAGIC); - // num entries - checkCondition(testParcel.readInt() == 1); - - shouldReadKeysAsStringsCached = expectedKey.equals(testParcel.readString()); - } catch (RuntimeException e) { - shouldReadKeysAsStringsCached = Boolean.FALSE; - } finally { - testParcel.recycle(); - } - } - - return shouldReadKeysAsStringsCached; - } - - /** Throws an {@code IllegalStateException} if {@code condition} is false. */ - private static void checkCondition(boolean condition) { - if (!condition) { - throw new IllegalStateException(); - } - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayDriver.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayDriver.java deleted file mode 100644 index 58314821a..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayDriver.java +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.support.annotation.NonNull; - -import com.firebase.jobdispatcher.FirebaseJobDispatcher.ScheduleResult; - -/** - * GooglePlayDriver provides an implementation of Driver for devices with Google Play - * services installed. This backend does not do any availability checks and any uses should be - * guarded with a call to {@code GoogleApiAvailability#isGooglePlayServicesAvailable(android.content.Context)} - * - * @see - * GoogleApiAvailability - */ -public final class GooglePlayDriver implements Driver { - static final String BACKEND_PACKAGE = "com.google.android.gms"; - private final static String ACTION_SCHEDULE = "com.google.android.gms.gcm.ACTION_SCHEDULE"; - - private final static String BUNDLE_PARAM_SCHEDULER_ACTION = "scheduler_action"; - private final static String BUNDLE_PARAM_TAG = "tag"; - private final static String BUNDLE_PARAM_TOKEN = "app"; - private final static String BUNDLE_PARAM_COMPONENT = "component"; - - private final static String SCHEDULER_ACTION_SCHEDULE_TASK = "SCHEDULE_TASK"; - private final static String SCHEDULER_ACTION_CANCEL_TASK = "CANCEL_TASK"; - private final static String SCHEDULER_ACTION_CANCEL_ALL = "CANCEL_ALL"; - private static final String INTENT_PARAM_SOURCE = "source"; - private static final String INTENT_PARAM_SOURCE_VERSION = "source_version"; - - private static final int JOB_DISPATCHER_SOURCE_CODE = 1 << 3; - private static final int JOB_DISPATCHER_SOURCE_VERSION_CODE = 1; - - private final JobValidator mValidator; - /** - * The application Context. Used to send broadcasts. - */ - private final Context mContext; - /** - * A PendingIntent from this package. Passed inside the broadcast so the receiver can verify the - * sender's package. - */ - private final PendingIntent mToken; - /** - * Turns Jobs into Bundles. - */ - private final GooglePlayJobWriter mWriter; - - /** - * Instantiates a new GooglePlayDriver. - */ - public GooglePlayDriver(Context context) { - mContext = context; - mToken = PendingIntent.getBroadcast(context, 0, new Intent(), 0); - mWriter = new GooglePlayJobWriter(); - mValidator = new DefaultJobValidator(context); - } - - @Override - public boolean isAvailable() { - ApplicationInfo applicationInfo = null; - try { - applicationInfo = mContext.getPackageManager().getApplicationInfo(BACKEND_PACKAGE, 0); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - return applicationInfo != null && applicationInfo.enabled; - } - - - /** - * Schedules the provided Job. - */ - @Override - @ScheduleResult - public int schedule(@NonNull Job job) { - mContext.sendBroadcast(createScheduleRequest(job)); - - return FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS; - } - - @Override - public int cancel(@NonNull String tag) { - mContext.sendBroadcast(createCancelRequest(tag)); - - return FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS; - } - - @Override - public int cancelAll() { - mContext.sendBroadcast(createBatchCancelRequest()); - - return FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS; - } - - @NonNull - protected Intent createCancelRequest(@NonNull String tag) { - Intent cancelReq = createSchedulerIntent(SCHEDULER_ACTION_CANCEL_TASK); - cancelReq.putExtra(BUNDLE_PARAM_TAG, tag); - cancelReq.putExtra(BUNDLE_PARAM_COMPONENT, new ComponentName(mContext, getReceiverClass())); - return cancelReq; - } - - @NonNull - protected Intent createBatchCancelRequest() { - Intent cancelReq = createSchedulerIntent(SCHEDULER_ACTION_CANCEL_ALL); - cancelReq.putExtra(BUNDLE_PARAM_COMPONENT, new ComponentName(mContext, getReceiverClass())); - return cancelReq; - } - - @NonNull - protected Class getReceiverClass() { - return GooglePlayReceiver.class; - } - - @NonNull - @Override - public JobValidator getValidator() { - return mValidator; - } - - @NonNull - private Intent createScheduleRequest(JobParameters job) { - Intent scheduleReq = createSchedulerIntent(SCHEDULER_ACTION_SCHEDULE_TASK); - scheduleReq.putExtras(mWriter.writeToBundle(job, scheduleReq.getExtras())); - return scheduleReq; - } - - @NonNull - private Intent createSchedulerIntent(String schedulerAction) { - Intent scheduleReq = new Intent(ACTION_SCHEDULE); - - scheduleReq.setPackage(BACKEND_PACKAGE); - scheduleReq.putExtra(BUNDLE_PARAM_SCHEDULER_ACTION, schedulerAction); - scheduleReq.putExtra(BUNDLE_PARAM_TOKEN, mToken); - scheduleReq.putExtra(INTENT_PARAM_SOURCE, JOB_DISPATCHER_SOURCE_CODE); - scheduleReq.putExtra(INTENT_PARAM_SOURCE_VERSION, JOB_DISPATCHER_SOURCE_VERSION_CODE); - - return scheduleReq; - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayJobCallback.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayJobCallback.java deleted file mode 100644 index 7f47fe598..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayJobCallback.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.os.IBinder; -import android.os.Parcel; -import android.os.RemoteException; - -/** - * Wraps the GooglePlay-specific callback class in a JobCallback-compatible interface. - */ -/* package */ final class GooglePlayJobCallback implements JobCallback { - - private static final String DESCRIPTOR = "com.google.android.gms.gcm.INetworkTaskCallback"; - /** The only supported transaction ID. */ - private static final int TRANSACTION_TASK_FINISHED = IBinder.FIRST_CALL_TRANSACTION + 1; - - private final IBinder mRemote; - - public GooglePlayJobCallback(IBinder binder) { - mRemote = binder; - } - - @Override - public void jobFinished(@JobService.JobResult int status) { - Parcel request = Parcel.obtain(); - Parcel response = Parcel.obtain(); - try { - request.writeInterfaceToken(DESCRIPTOR); - request.writeInt(status); - - mRemote.transact(TRANSACTION_TASK_FINISHED, request, response, 0); - - response.readException(); - } catch (RemoteException e) { - throw new RuntimeException(e); - } finally { - request.recycle(); - response.recycle(); - } - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayJobWriter.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayJobWriter.java deleted file mode 100644 index 0389f90f7..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayJobWriter.java +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.IntDef; -import android.support.annotation.VisibleForTesting; -import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger; -import com.firebase.jobdispatcher.RetryStrategy.RetryPolicy; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/* package */ final class GooglePlayJobWriter { - - static final String REQUEST_PARAM_UPDATE_CURRENT = "update_current"; - static final String REQUEST_PARAM_EXTRAS = "extras"; - static final String REQUEST_PARAM_PERSISTED = "persisted"; - static final String REQUEST_PARAM_REQUIRED_NETWORK = "requiredNetwork"; - static final String REQUEST_PARAM_REQUIRES_CHARGING = "requiresCharging"; - static final String REQUEST_PARAM_REQUIRES_IDLE = "requiresIdle"; - static final String REQUEST_PARAM_RETRY_STRATEGY = "retryStrategy"; - static final String REQUEST_PARAM_SERVICE = "service"; - static final String REQUEST_PARAM_TAG = "tag"; - - static final String REQUEST_PARAM_RETRY_STRATEGY_INITIAL_BACKOFF_SECONDS = - "initial_backoff_seconds"; - static final String REQUEST_PARAM_RETRY_STRATEGY_MAXIMUM_BACKOFF_SECONDS = - "maximum_backoff_seconds"; - static final String REQUEST_PARAM_RETRY_STRATEGY_POLICY = "retry_policy"; - - static final String REQUEST_PARAM_TRIGGER_TYPE = "trigger_type"; - static final String REQUEST_PARAM_TRIGGER_WINDOW_END = "window_end"; - static final String REQUEST_PARAM_TRIGGER_WINDOW_FLEX = "period_flex"; - static final String REQUEST_PARAM_TRIGGER_WINDOW_PERIOD = "period"; - static final String REQUEST_PARAM_TRIGGER_WINDOW_START = "window_start"; - - @VisibleForTesting - /* package */ static final int LEGACY_RETRY_POLICY_EXPONENTIAL = 0; - @VisibleForTesting - /* package */ static final int LEGACY_RETRY_POLICY_LINEAR = 1; - @VisibleForTesting - /* package */ final static int LEGACY_NETWORK_UNMETERED = 1; - @VisibleForTesting - /* package */ final static int LEGACY_NETWORK_CONNECTED = 0; - @VisibleForTesting - /* package */ final static int LEGACY_NETWORK_ANY = 2; - - private JobCoder jobCoder = new JobCoder(BundleProtocol.PACKED_PARAM_BUNDLE_PREFIX, false); - - private static void writeExecutionWindowTriggerToBundle(JobParameters job, Bundle b, - JobTrigger.ExecutionWindowTrigger trigger) { - - b.putInt(REQUEST_PARAM_TRIGGER_TYPE, BundleProtocol.TRIGGER_TYPE_EXECUTION_WINDOW); - - if (job.isRecurring()) { - b.putLong(REQUEST_PARAM_TRIGGER_WINDOW_PERIOD, - trigger.getWindowEnd()); - b.putLong(REQUEST_PARAM_TRIGGER_WINDOW_FLEX, - trigger.getWindowEnd() - trigger.getWindowStart()); - } else { - b.putLong(REQUEST_PARAM_TRIGGER_WINDOW_START, - trigger.getWindowStart()); - b.putLong(REQUEST_PARAM_TRIGGER_WINDOW_END, - trigger.getWindowEnd()); - } - } - - private static void writeImmediateTriggerToBundle(Bundle b) { - b.putInt(REQUEST_PARAM_TRIGGER_TYPE, BundleProtocol.TRIGGER_TYPE_IMMEDIATE); - b.putLong(REQUEST_PARAM_TRIGGER_WINDOW_START, 0); - b.putLong(REQUEST_PARAM_TRIGGER_WINDOW_END, 30); - } - - private void writeContentUriTriggerToBundle(Bundle data, ContentUriTrigger uriTrigger) { - data.putInt(BundleProtocol.PACKED_PARAM_TRIGGER_TYPE, - BundleProtocol.TRIGGER_TYPE_CONTENT_URI); - - int size = uriTrigger.getUris().size(); - int[] flagsArray = new int[size]; - Uri[] uriArray = new Uri[size]; - for (int i = 0; i < size; i++) { - ObservedUri uri = uriTrigger.getUris().get(i); - flagsArray[i] = uri.getFlags(); - uriArray[i] = uri.getUri(); - } - data.putIntArray(BundleProtocol.PACKED_PARAM_CONTENT_URI_FLAGS_ARRAY, flagsArray); - data.putParcelableArray(BundleProtocol.PACKED_PARAM_CONTENT_URI_ARRAY, uriArray); - } - - public Bundle writeToBundle(JobParameters job, Bundle b) { - b.putString(REQUEST_PARAM_TAG, job.getTag()); - b.putBoolean(REQUEST_PARAM_UPDATE_CURRENT, job.shouldReplaceCurrent()); - - boolean persisted = job.getLifetime() == Lifetime.FOREVER; - b.putBoolean(REQUEST_PARAM_PERSISTED, persisted); - b.putString(REQUEST_PARAM_SERVICE, GooglePlayReceiver.class.getName()); - - writeTriggerToBundle(job, b); - writeConstraintsToBundle(job, b); - writeRetryStrategyToBundle(job, b); - - // Embed the job spec (minus extras) into the extras (under a prefix) - Bundle extras = job.getExtras(); - if (extras == null) { - extras = new Bundle(); - } - b.putBundle(REQUEST_PARAM_EXTRAS, jobCoder.encode(job, extras)); - - return b; - } - - private void writeRetryStrategyToBundle(JobParameters job, Bundle b) { - RetryStrategy strategy = job.getRetryStrategy(); - - Bundle rb = new Bundle(); - rb.putInt(REQUEST_PARAM_RETRY_STRATEGY_POLICY, - convertRetryPolicyToLegacyVersion(strategy.getPolicy())); - rb.putInt(REQUEST_PARAM_RETRY_STRATEGY_INITIAL_BACKOFF_SECONDS, - strategy.getInitialBackoff()); - rb.putInt(REQUEST_PARAM_RETRY_STRATEGY_MAXIMUM_BACKOFF_SECONDS, - strategy.getMaximumBackoff()); - - b.putBundle(REQUEST_PARAM_RETRY_STRATEGY, rb); - } - - private int convertRetryPolicyToLegacyVersion(@RetryPolicy int policy) { - switch (policy) { - case RetryStrategy.RETRY_POLICY_LINEAR: - return LEGACY_RETRY_POLICY_LINEAR; - - case RetryStrategy.RETRY_POLICY_EXPONENTIAL: - // fallthrough - default: - return LEGACY_RETRY_POLICY_EXPONENTIAL; - } - } - - private void writeTriggerToBundle(JobParameters job, Bundle b) { - final JobTrigger trigger = job.getTrigger(); - - if (trigger == Trigger.NOW) { - writeImmediateTriggerToBundle(b); - } else if (trigger instanceof JobTrigger.ExecutionWindowTrigger) { - writeExecutionWindowTriggerToBundle(job, b, (JobTrigger.ExecutionWindowTrigger) trigger); - } else if (trigger instanceof JobTrigger.ContentUriTrigger) { - writeContentUriTriggerToBundle(b, (JobTrigger.ContentUriTrigger) trigger); - } else { - throw new IllegalArgumentException("Unknown trigger: " + trigger.getClass()); - } - } - - private void writeConstraintsToBundle(JobParameters job, Bundle b) { - int c = Constraint.compact(job.getConstraints()); - - b.putBoolean(REQUEST_PARAM_REQUIRES_CHARGING, - (c & Constraint.DEVICE_CHARGING) == Constraint.DEVICE_CHARGING); - b.putBoolean(REQUEST_PARAM_REQUIRES_IDLE, - (c & Constraint.DEVICE_IDLE) == Constraint.DEVICE_IDLE); - b.putInt(REQUEST_PARAM_REQUIRED_NETWORK, convertConstraintsToLegacyNetConstant(c)); - } - - /** - * Converts a bitmap of Constraint values into a LegacyNetworkConstraint constant (int). - */ - @LegacyNetworkConstant - private int convertConstraintsToLegacyNetConstant(int constraintMap) { - int reqNet = LEGACY_NETWORK_ANY; - - reqNet = (constraintMap & Constraint.ON_ANY_NETWORK) == Constraint.ON_ANY_NETWORK - ? LEGACY_NETWORK_CONNECTED - : reqNet; - - reqNet = (constraintMap & Constraint.ON_UNMETERED_NETWORK) == Constraint.ON_UNMETERED_NETWORK - ? LEGACY_NETWORK_UNMETERED - : reqNet; - - return reqNet; - } - - @IntDef({LEGACY_NETWORK_ANY, LEGACY_NETWORK_CONNECTED, LEGACY_NETWORK_UNMETERED}) - @Retention(RetentionPolicy.SOURCE) - private @interface LegacyNetworkConstant {} -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayMessageHandler.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayMessageHandler.java deleted file mode 100644 index 664805f67..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayMessageHandler.java +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static com.firebase.jobdispatcher.GooglePlayJobWriter.REQUEST_PARAM_TAG; -import static com.firebase.jobdispatcher.GooglePlayReceiver.TAG; - -import android.annotation.TargetApi; -import android.app.AppOpsManager; -import android.content.Context; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.Messenger; -import android.util.Log; -import com.firebase.jobdispatcher.JobInvocation.Builder; - -/** - * A messenger for communication with GCM Network Scheduler. - */ -@TargetApi(Build.VERSION_CODES.LOLLIPOP) -class GooglePlayMessageHandler extends Handler { - - static final int MSG_START_EXEC = 1; - static final int MSG_STOP_EXEC = 2; - static final int MSG_RESULT = 3; - private static final int MSG_INIT = 4; - private final GooglePlayReceiver googlePlayReceiver; - - public GooglePlayMessageHandler(Looper mainLooper, GooglePlayReceiver googlePlayReceiver) { - super(mainLooper); - this.googlePlayReceiver = googlePlayReceiver; - } - - @Override - public void handleMessage(Message message) { - if (message == null) { - return; - } - - AppOpsManager appOpsManager = (AppOpsManager) googlePlayReceiver.getApplicationContext() - .getSystemService(Context.APP_OPS_SERVICE); - try { - appOpsManager.checkPackage(message.sendingUid, GooglePlayDriver.BACKEND_PACKAGE); - } catch (SecurityException e) { - Log.e(TAG, "Message was not sent from GCM."); - return; - } - - switch (message.what) { - case MSG_START_EXEC: - handleStartMessage(message); - break; - - case MSG_STOP_EXEC: - handleStopMessage(message); - break; - - case MSG_INIT: - // Not implemented. - break; - - default: - Log.e(TAG, "Unrecognized message received: " + message); - break; - } - } - - private void handleStartMessage(Message message) { - final Bundle data = message.getData(); - - final Messenger replyTo = message.replyTo; - String tag = data.getString(REQUEST_PARAM_TAG); - if (replyTo == null || tag == null) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Invalid start execution message."); - } - return; - } - - GooglePlayMessengerCallback messengerCallback = - new GooglePlayMessengerCallback(replyTo, tag); - JobInvocation jobInvocation = googlePlayReceiver.prepareJob(messengerCallback, data); - googlePlayReceiver.getExecutionDelegator().executeJob(jobInvocation); - } - - private void handleStopMessage(Message message) { - Builder builder = GooglePlayReceiver.getJobCoder().decode(message.getData()); - if (builder == null) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Invalid stop execution message."); - } - return; - } - JobInvocation job = builder.build(); - googlePlayReceiver.getExecutionDelegator().stopJob(job); - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayMessengerCallback.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayMessengerCallback.java deleted file mode 100644 index 06fb153f3..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayMessengerCallback.java +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static com.firebase.jobdispatcher.GooglePlayJobWriter.REQUEST_PARAM_TAG; - -import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; -import android.support.annotation.NonNull; -import com.firebase.jobdispatcher.JobService.JobResult; - -/** - * Wraps the GooglePlay messenger in a JobCallback-compatible interface. - */ -class GooglePlayMessengerCallback implements JobCallback { - - private final Messenger messenger; - private final String tag; - - GooglePlayMessengerCallback(Messenger messenger, String tag) { - this.messenger = messenger; - this.tag = tag; - } - - @Override - public void jobFinished(@JobResult int status) { - try { - messenger.send(createResultMessage(status)); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - @NonNull - private Message createResultMessage(int result) { - final Message msg = Message.obtain(); - msg.what = GooglePlayMessageHandler.MSG_RESULT; - msg.arg1 = result; - - Bundle b = new Bundle(); - b.putString(REQUEST_PARAM_TAG, tag); - msg.setData(b); - return msg; - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayReceiver.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayReceiver.java deleted file mode 100644 index e8dc60609..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/GooglePlayReceiver.java +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.app.Service; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.os.IBinder; -import android.os.Looper; -import android.os.Messenger; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; -import android.support.v4.util.SimpleArrayMap; -import android.util.Log; -import android.util.Pair; -import com.firebase.jobdispatcher.Job.Builder; -import com.firebase.jobdispatcher.JobService.JobResult; -import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger; - -/** - * Handles incoming execute requests from the GooglePlay driver and forwards them to your Service. - */ -public class GooglePlayReceiver extends Service implements ExecutionDelegator.JobFinishedCallback { - /** - * Logging tag. - */ - /* package */ static final String TAG = "FJD.GooglePlayReceiver"; - /** - * The action sent by Google Play services that triggers job execution. - */ - @VisibleForTesting - static final String ACTION_EXECUTE = "com.google.android.gms.gcm.ACTION_TASK_READY"; - - /** Action sent by Google Play services when your app has been updated. */ - @VisibleForTesting - static final String ACTION_INITIALIZE = "com.google.android.gms.gcm.SERVICE_ACTION_INITIALIZE"; - - private static final String ERROR_NULL_INTENT = "Null Intent passed, terminating"; - private static final String ERROR_UNKNOWN_ACTION = "Unknown action received, terminating"; - private static final String ERROR_NO_DATA = "No data provided, terminating"; - - private static final JobCoder prefixedCoder = - new JobCoder(BundleProtocol.PACKED_PARAM_BUNDLE_PREFIX, true); - - private final GooglePlayCallbackExtractor callbackExtractor = new GooglePlayCallbackExtractor(); - - /** - * The single Messenger that's returned from valid onBind requests. Guarded by intrinsic lock. - */ - @VisibleForTesting - Messenger serviceMessenger; - - /** - * Driver for rescheduling jobs. Guarded by intrinsic lock. - */ - @VisibleForTesting - Driver driver; - - /** - * Guarded by intrinsic lock. - */ - @VisibleForTesting - ValidationEnforcer validationEnforcer; - - /** - * The ExecutionDelegator used to communicate with client JobServices. - * Guarded by intrinsic lock. - */ - private ExecutionDelegator executionDelegator; - - /** - * The most recent startId passed to onStartCommand. - * Guarded by intrinsic lock. - */ - private int latestStartId; - - /** - * Endpoint (String) -> Tag (String) -> JobCallback - */ - private SimpleArrayMap> callbacks = - new SimpleArrayMap<>(1); - - private static void sendResultSafely(JobCallback callback, int result) { - try { - callback.jobFinished(result); - } catch (Throwable e) { - Log.e(TAG, "Encountered error running callback", e.getCause()); - } - } - - @Override - public final int onStartCommand(Intent intent, int flags, int startId) { - try { - super.onStartCommand(intent, flags, startId); - - if (intent == null) { - Log.w(TAG, ERROR_NULL_INTENT); - return START_NOT_STICKY; - } - - String action = intent.getAction(); - if (ACTION_EXECUTE.equals(action)) { - getExecutionDelegator().executeJob(prepareJob(intent)); - return START_NOT_STICKY; - } else if (ACTION_INITIALIZE.equals(action)) { - return START_NOT_STICKY; - } - - Log.e(TAG, ERROR_UNKNOWN_ACTION); - return START_NOT_STICKY; - } finally { - synchronized (this) { - latestStartId = startId; - if (callbacks.isEmpty()) { - stopSelf(latestStartId); - } - } - } - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - // Only Lollipop+ supports UID checking messages, so we can't trust this system on older - // platforms. - if (intent == null - || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP - || !ACTION_EXECUTE.equals(intent.getAction())) { - return null; - } - return getServiceMessenger().getBinder(); - } - - private synchronized Messenger getServiceMessenger() { - if (serviceMessenger == null) { - serviceMessenger = - new Messenger(new GooglePlayMessageHandler(Looper.getMainLooper(), this)); - } - return serviceMessenger; - } - - /* package */ synchronized ExecutionDelegator getExecutionDelegator() { - if (executionDelegator == null) { - executionDelegator = new ExecutionDelegator(this, this); - } - return executionDelegator; - } - - @NonNull - private synchronized Driver getGooglePlayDriver() { - if (driver == null) { - driver = new GooglePlayDriver(getApplicationContext()); - } - return driver; - } - - @NonNull - private synchronized ValidationEnforcer getValidationEnforcer() { - if (validationEnforcer == null) { - validationEnforcer = new ValidationEnforcer(getGooglePlayDriver().getValidator()); - } - return validationEnforcer; - } - - @Nullable - @VisibleForTesting - JobInvocation prepareJob(Intent intent) { - Bundle intentExtras = intent.getExtras(); - if (intentExtras == null) { - Log.e(TAG, ERROR_NO_DATA); - return null; - } - - // get the callback first. If we don't have this we can't talk back to the backend. - Pair extraction = callbackExtractor.extractCallback(intentExtras); - if (extraction == null) { - Log.i(TAG, "no callback found"); - return null; - } - return prepareJob(extraction.first, extraction.second); - } - - @Nullable - synchronized JobInvocation prepareJob(JobCallback callback, Bundle bundle) { - JobInvocation job = prefixedCoder.decodeIntentBundle(bundle); - if (job == null) { - Log.e(TAG, "unable to decode job"); - sendResultSafely(callback, JobService.RESULT_FAIL_NORETRY); - return null; - } - SimpleArrayMap map = callbacks.get(job.getService()); - if (map == null) { - map = new SimpleArrayMap<>(1); - callbacks.put(job.getService(), map); - } - - map.put(job.getTag(), callback); - - return job; - } - - @Override - public synchronized void onJobFinished(@NonNull JobInvocation js, @JobResult int result) { - try { - SimpleArrayMap map = callbacks.get(js.getService()); - if (map == null) { - return; - } - JobCallback callback = map.remove(js.getTag()); - if (callback == null) { - return; - } - if (map.isEmpty()) { - callbacks.remove(js.getService()); - } - - if (needsToBeRescheduled(js, result)) { - reschedule(js); - } else { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "sending jobFinished for " + js.getTag() + " = " + result); - } - sendResultSafely(callback, result); - } - } finally { - if (callbacks.isEmpty()) { - // Safe to call stopSelf, even if we're being bound to - stopSelf(latestStartId); - } - } - } - - private void reschedule(JobInvocation jobInvocation) { - Job job = new Builder(getValidationEnforcer(), jobInvocation) - .setReplaceCurrent(true) - .build(); - - getGooglePlayDriver().schedule(job); - } - - /** - * Recurring content URI triggered jobs need to be rescheduled when execution is finished. - * - *

GooglePlay does not support recurring content URI triggered jobs. - * - *

{@link JobService#RESULT_FAIL_RETRY} needs to be sent or current triggered URIs will be - * lost. - */ - private static boolean needsToBeRescheduled(JobParameters job, int result) { - return job.isRecurring() - && job.getTrigger() instanceof ContentUriTrigger - && result != JobService.RESULT_FAIL_RETRY; - } - - static JobCoder getJobCoder() { - return prefixedCoder; - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Job.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Job.java deleted file mode 100644 index 692404fe9..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Job.java +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import com.firebase.jobdispatcher.Constraint.JobConstraint; - -/** - * Job is the embodiment of a unit of work and an associated set of triggers, settings, and runtime - * constraints. - */ -public final class Job implements JobParameters { - private final String mService; - private final String mTag; - private final JobTrigger mTrigger; - private final RetryStrategy mRetryStrategy; - private final int mLifetime; - private final boolean mRecurring; - private final int[] mConstraints; - private final boolean mReplaceCurrent; - private Bundle mExtras; - - private Job(Builder builder) { - mService = builder.mServiceClassName; - mExtras = builder.mExtras; - mTag = builder.mTag; - mTrigger = builder.mTrigger; - mRetryStrategy = builder.mRetryStrategy; - mLifetime = builder.mLifetime; - mRecurring = builder.mRecurring; - mConstraints = builder.mConstraints != null ? builder.mConstraints : new int[0]; - mReplaceCurrent = builder.mReplaceCurrent; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public int[] getConstraints() { - return mConstraints; - } - - /** - * {@inheritDoc} - */ - @Nullable - @Override - public Bundle getExtras() { - return mExtras; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public RetryStrategy getRetryStrategy() { - return mRetryStrategy; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean shouldReplaceCurrent() { - return mReplaceCurrent; - } - - @Nullable - @Override - public TriggerReason getTriggerReason() { - return null; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public String getTag() { - return mTag; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public JobTrigger getTrigger() { - return mTrigger; - } - - /** - * {@inheritDoc} - */ - @Override - public int getLifetime() { - return mLifetime; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isRecurring() { - return mRecurring; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public String getService() { - return mService; - } - - /** - * A class that understands how to build a {@link Job}. Retrieved by calling - * {@link FirebaseJobDispatcher#newJobBuilder()}. - */ - public final static class Builder implements JobParameters { - private final ValidationEnforcer mValidator; - - private String mServiceClassName; - private Bundle mExtras; - private String mTag; - private JobTrigger mTrigger = Trigger.NOW; - private int mLifetime = Lifetime.UNTIL_NEXT_BOOT; - private int[] mConstraints; - - private RetryStrategy mRetryStrategy = RetryStrategy.DEFAULT_EXPONENTIAL; - private boolean mReplaceCurrent = false; - private boolean mRecurring = false; - - Builder(ValidationEnforcer validator) { - mValidator = validator; - } - - Builder(ValidationEnforcer validator, JobParameters job) { - mValidator = validator; - - mTag = job.getTag(); - mServiceClassName = job.getService(); - mTrigger = job.getTrigger(); - mRecurring = job.isRecurring(); - mLifetime = job.getLifetime(); - mConstraints = job.getConstraints(); - mExtras = job.getExtras(); - mRetryStrategy = job.getRetryStrategy(); - } - - /** - * Adds the provided constraint to the current list of runtime constraints. - */ - public Builder addConstraint(@JobConstraint int constraint) { - // Create a new, longer constraints array - int[] newConstraints = new int[mConstraints == null ? 1 : mConstraints.length + 1]; - - if (mConstraints != null && mConstraints.length != 0) { - // Copy all the old values over - System.arraycopy(mConstraints, 0, newConstraints, 0, mConstraints.length); - } - - // add the new value - newConstraints[newConstraints.length - 1] = constraint; - // update the pointer - mConstraints = newConstraints; - - return this; - } - - /** - * Sets whether this Job should replace pre-existing Jobs with the same tag. - */ - public Builder setReplaceCurrent(boolean replaceCurrent) { - mReplaceCurrent = replaceCurrent; - - return this; - } - - /** - * Builds the Job, using the settings provided so far. - * - * @throws ValidationEnforcer.ValidationException - */ - public Job build() { - mValidator.ensureValid(this); - - return new Job(this); - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public String getService() { - return mServiceClassName; - } - - /** - * Sets the backing JobService class for the Job. See {@link #getService()}. - */ - public Builder setService(Class serviceClass) { - mServiceClassName = serviceClass == null ? null : serviceClass.getName(); - - return this; - } - - /** - * Sets the backing JobService class name for the Job. See {@link #getService()}. - * - *

Should not be exposed, for internal use only. - */ - Builder setServiceName(String serviceClassName) { - mServiceClassName = serviceClassName; - - return this; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public String getTag() { - return mTag; - } - - /** - * Sets the unique String tag used to identify the Job. See {@link #getTag()}. - */ - public Builder setTag(String tag) { - mTag = tag; - - return this; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public JobTrigger getTrigger() { - return mTrigger; - } - - /** - * Sets the Trigger used for the Job. See {@link #getTrigger()}. - */ - public Builder setTrigger(JobTrigger trigger) { - mTrigger = trigger; - - return this; - } - - /** - * {@inheritDoc} - */ - @Override - @Lifetime.LifetimeConstant - public int getLifetime() { - return mLifetime; - } - - /** - * Sets the Job's lifetime, or how long it should persist. See {@link #getLifetime()}. - */ - public Builder setLifetime(@Lifetime.LifetimeConstant int lifetime) { - mLifetime = lifetime; - - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isRecurring() { - return mRecurring; - } - - /** - * Sets whether the job should recur. The default is false. - */ - public Builder setRecurring(boolean recurring) { - mRecurring = recurring; - - return this; - } - - /** - * {@inheritDoc} - */ - @Override - @JobConstraint - public int[] getConstraints() { - return mConstraints == null ? new int[]{} : mConstraints; - } - - /** - * Sets the Job's runtime constraints. See {@link #getConstraints()}. - */ - public Builder setConstraints(@JobConstraint int... constraints) { - mConstraints = constraints; - - return this; - } - - /** - * {@inheritDoc} - */ - @Nullable - @Override - public Bundle getExtras() { - return mExtras; - } - - /** - * Sets the user-defined extras associated with the Job. See {@link #getExtras()}. - */ - public Builder setExtras(Bundle extras) { - mExtras = extras; - - return this; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public RetryStrategy getRetryStrategy() { - return mRetryStrategy; - } - - /** - * Set the RetryStrategy used for the Job. See {@link #getRetryStrategy()}. - */ - public Builder setRetryStrategy(RetryStrategy retryStrategy) { - mRetryStrategy = retryStrategy; - - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean shouldReplaceCurrent() { - return mReplaceCurrent; - } - - @Nullable - @Override - public TriggerReason getTriggerReason() { - return null; - } - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobCallback.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobCallback.java deleted file mode 100644 index adeb82eaf..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobCallback.java +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -/** - * JobCallback describes an object that knows how to send a JobResult back to the underlying - * execution driver. - */ -public interface JobCallback { - /** - * @throws RuntimeException - */ - void jobFinished(@JobService.JobResult int status); -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobCoder.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobCoder.java deleted file mode 100644 index e5523587e..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobCoder.java +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static com.firebase.jobdispatcher.Constraint.compact; -import static com.firebase.jobdispatcher.Constraint.uncompact; -import static com.firebase.jobdispatcher.ExecutionDelegator.TAG; - -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Log; -import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * JobCoder is a tool to encode and decode JobSpecs from Bundles. - */ -/* package */ final class JobCoder { - private final boolean includeExtras; - private final String prefix; - - private static final String JSON_URI_FLAGS = "uri_flags"; - private static final String JSON_URIS = "uris"; - - JobCoder(String prefix, boolean includeExtras) { - this.includeExtras = includeExtras; - this.prefix = prefix; - } - - @NonNull - Bundle encode(@NonNull JobParameters jobParameters, @NonNull Bundle data) { - if (data == null) { - throw new IllegalArgumentException("Unexpected null Bundle provided"); - } - - data.putInt(prefix + BundleProtocol.PACKED_PARAM_LIFETIME, - jobParameters.getLifetime()); - data.putBoolean(prefix + BundleProtocol.PACKED_PARAM_RECURRING, - jobParameters.isRecurring()); - data.putBoolean(prefix + BundleProtocol.PACKED_PARAM_REPLACE_CURRENT, - jobParameters.shouldReplaceCurrent()); - data.putString(prefix + BundleProtocol.PACKED_PARAM_TAG, - jobParameters.getTag()); - data.putString(prefix + BundleProtocol.PACKED_PARAM_SERVICE, - jobParameters.getService()); - data.putInt(prefix + BundleProtocol.PACKED_PARAM_CONSTRAINTS, - compact(jobParameters.getConstraints())); - - if (includeExtras) { - data.putBundle(prefix + BundleProtocol.PACKED_PARAM_EXTRAS, - jobParameters.getExtras()); - } - - encodeTrigger(jobParameters.getTrigger(), data); - encodeRetryStrategy(jobParameters.getRetryStrategy(), data); - - return data; - } - - JobInvocation decodeIntentBundle(@NonNull Bundle bundle) { - if (bundle == null) { - Log.e(TAG, "Unexpected null Bundle provided"); - return null; - } - - Bundle taskExtras = bundle.getBundle(GooglePlayJobWriter.REQUEST_PARAM_EXTRAS); - if (taskExtras == null) { - return null; - } - - JobInvocation.Builder builder = decode(taskExtras); - - List triggeredContentUris = - bundle.getParcelableArrayList(BundleProtocol.PACKED_PARAM_TRIGGERED_URIS); - if (triggeredContentUris != null) { - builder.setTriggerReason(new TriggerReason(triggeredContentUris)); - } - return builder.build(); - } - - @Nullable - public JobInvocation.Builder decode(@NonNull Bundle data) { - if (data == null) { - throw new IllegalArgumentException("Unexpected null Bundle provided"); - } - - boolean recur = data.getBoolean(prefix + BundleProtocol.PACKED_PARAM_RECURRING); - boolean replaceCur = data.getBoolean(prefix + BundleProtocol.PACKED_PARAM_REPLACE_CURRENT); - int lifetime = data.getInt(prefix + BundleProtocol.PACKED_PARAM_LIFETIME); - int[] constraints = uncompact(data.getInt(prefix + BundleProtocol.PACKED_PARAM_CONSTRAINTS)); - - JobTrigger trigger = decodeTrigger(data); - RetryStrategy retryStrategy = decodeRetryStrategy(data); - - String tag = data.getString(prefix + BundleProtocol.PACKED_PARAM_TAG); - String service = data.getString(prefix + BundleProtocol.PACKED_PARAM_SERVICE); - - if (tag == null || service == null || trigger == null || retryStrategy == null) { - return null; - } - - JobInvocation.Builder builder = new JobInvocation.Builder(); - builder.setTag(tag); - builder.setService(service); - builder.setTrigger(trigger); - builder.setRetryStrategy(retryStrategy); - builder.setRecurring(recur); - //noinspection WrongConstant - builder.setLifetime(lifetime); - //noinspection WrongConstant - builder.setConstraints(constraints); - builder.setReplaceCurrent(replaceCur); - - // repack the taskExtras - builder.addExtras(data); - return builder; - } - - @NonNull - private JobTrigger decodeTrigger(Bundle data) { - switch (data.getInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_TYPE)) { - case BundleProtocol.TRIGGER_TYPE_IMMEDIATE: - return Trigger.NOW; - - case BundleProtocol.TRIGGER_TYPE_EXECUTION_WINDOW: - return Trigger.executionWindow( - data.getInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_WINDOW_START), - data.getInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_WINDOW_END)); - - case BundleProtocol.TRIGGER_TYPE_CONTENT_URI: - String uris = data.getString(prefix + BundleProtocol.PACKED_PARAM_OBSERVED_URI); - List observedUris = convertJsonToObservedUris(uris); - return Trigger.contentUriTrigger(Collections.unmodifiableList(observedUris)); - - default: - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Unsupported trigger."); - } - return null; - } - } - - private void encodeTrigger(JobTrigger trigger, Bundle data) { - if (trigger == Trigger.NOW) { - data.putInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_TYPE, - BundleProtocol.TRIGGER_TYPE_IMMEDIATE); - } else if (trigger instanceof JobTrigger.ExecutionWindowTrigger) { - JobTrigger.ExecutionWindowTrigger t = (JobTrigger.ExecutionWindowTrigger) trigger; - - data.putInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_TYPE, - BundleProtocol.TRIGGER_TYPE_EXECUTION_WINDOW); - data.putInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_WINDOW_START, - t.getWindowStart()); - data.putInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_WINDOW_END, - t.getWindowEnd()); - } else if (trigger instanceof JobTrigger.ContentUriTrigger) { - data.putInt(prefix + BundleProtocol.PACKED_PARAM_TRIGGER_TYPE, - BundleProtocol.TRIGGER_TYPE_CONTENT_URI); - ContentUriTrigger uriTrigger = (ContentUriTrigger) trigger; - String jsonTrigger = convertObservedUrisToJsonString(uriTrigger.getUris()); - data.putString(prefix + BundleProtocol.PACKED_PARAM_OBSERVED_URI, jsonTrigger); - } else { - throw new IllegalArgumentException("Unsupported trigger."); - } - } - - private RetryStrategy decodeRetryStrategy(Bundle data) { - int policy = data.getInt(prefix + BundleProtocol.PACKED_PARAM_RETRY_STRATEGY_POLICY); - if (policy != RetryStrategy.RETRY_POLICY_EXPONENTIAL - && policy != RetryStrategy.RETRY_POLICY_LINEAR) { - - return RetryStrategy.DEFAULT_EXPONENTIAL; - } - - //noinspection WrongConstant - return new RetryStrategy( - policy, - data.getInt(prefix + BundleProtocol.PACKED_PARAM_RETRY_STRATEGY_INITIAL_BACKOFF_SECONDS), - data.getInt(prefix + BundleProtocol.PACKED_PARAM_RETRY_STRATEGY_MAXIMUM_BACKOFF_SECONDS)); - } - - private void encodeRetryStrategy(RetryStrategy retryStrategy, Bundle data) { - if (retryStrategy == null) { - retryStrategy = RetryStrategy.DEFAULT_EXPONENTIAL; - } - - data.putInt(prefix + BundleProtocol.PACKED_PARAM_RETRY_STRATEGY_POLICY, - retryStrategy.getPolicy()); - data.putInt(prefix + BundleProtocol.PACKED_PARAM_RETRY_STRATEGY_INITIAL_BACKOFF_SECONDS, - retryStrategy.getInitialBackoff()); - data.putInt(prefix + BundleProtocol.PACKED_PARAM_RETRY_STRATEGY_MAXIMUM_BACKOFF_SECONDS, - retryStrategy.getMaximumBackoff()); - } - - @NonNull - private String convertObservedUrisToJsonString(@NonNull List uris) { - JSONObject contentUris = new JSONObject(); - JSONArray jsonFlags = new JSONArray(); - JSONArray jsonUris = new JSONArray(); - for (ObservedUri uri : uris) { - jsonFlags.put(uri.getFlags()); - jsonUris.put(uri.getUri()); - } - try { - contentUris.put(JSON_URI_FLAGS, jsonFlags); - contentUris.put(JSON_URIS, jsonUris); - } catch (JSONException e) { - throw new RuntimeException(e); - } - return contentUris.toString(); - } - - @NonNull - private List convertJsonToObservedUris(@NonNull String contentUrisJson) { - List uris = new ArrayList<>(); - try { - JSONObject json = new JSONObject(contentUrisJson); - JSONArray jsonFlags = json.getJSONArray(JSON_URI_FLAGS); - JSONArray jsonUris = json.getJSONArray(JSON_URIS); - int length = jsonFlags.length(); - - for (int i = 0; i < length; i++) { - int flags = jsonFlags.getInt(i); - String uri = jsonUris.getString(i); - uris.add(new ObservedUri(Uri.parse(uri), flags)); - } - } catch (JSONException e) { - throw new RuntimeException(e); - } - return uris; - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobInvocation.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobInvocation.java deleted file mode 100644 index 08263e845..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobInvocation.java +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import com.firebase.jobdispatcher.Constraint.JobConstraint; - -/** - * An internal non-Job implementation of JobParameters. Passed to JobService invocations. - */ -/* package */ final class JobInvocation implements JobParameters { - - @NonNull - private final String mTag; - - @NonNull - private final String mService; - - @NonNull - private final JobTrigger mTrigger; - - private final boolean mRecurring; - - private final int mLifetime; - - @NonNull - @JobConstraint - private final int[] mConstraints; - - @NonNull - private final Bundle mExtras; - - private final RetryStrategy mRetryStrategy; - - private final boolean mReplaceCurrent; - - private final TriggerReason mTriggerReason; - - private JobInvocation(Builder builder) { - mTag = builder.mTag; - mService = builder.mService; - mTrigger = builder.mTrigger; - mRetryStrategy = builder.mRetryStrategy; - mRecurring = builder.mRecurring; - mLifetime = builder.mLifetime; - mConstraints = builder.mConstraints; - mExtras = builder.mExtras; - mReplaceCurrent = builder.mReplaceCurrent; - mTriggerReason = builder.mTriggerReason; - } - - @NonNull - @Override - public String getService() { - return mService; - } - - @NonNull - @Override - public String getTag() { - return mTag; - } - - @NonNull - @Override - public JobTrigger getTrigger() { - return mTrigger; - } - - @Override - public int getLifetime() { - return mLifetime; - } - - @Override - public boolean isRecurring() { - return mRecurring; - } - - @NonNull - @Override - public int[] getConstraints() { - return mConstraints; - } - - @NonNull - @Override - public Bundle getExtras() { - return mExtras; - } - - @NonNull - @Override - public RetryStrategy getRetryStrategy() { - return mRetryStrategy; - } - - @Override - public boolean shouldReplaceCurrent() { - return mReplaceCurrent; - } - - @Override - public TriggerReason getTriggerReason() { - return mTriggerReason; - } - - static final class Builder { - - @NonNull - private String mTag; - - @NonNull - private String mService; - - @NonNull - private JobTrigger mTrigger; - - private boolean mRecurring; - - private int mLifetime; - - @NonNull - @JobConstraint - private int[] mConstraints; - - @NonNull - private final Bundle mExtras = new Bundle(); - - private RetryStrategy mRetryStrategy; - - private boolean mReplaceCurrent; - - private TriggerReason mTriggerReason; - - JobInvocation build() { - if (mTag == null || mService == null || mTrigger == null) { - throw new IllegalArgumentException("Required fields were not populated."); - } - return new JobInvocation(this); - } - - public Builder setTag(@NonNull String mTag) { - this.mTag = mTag; - return this; - } - - public Builder setService(@NonNull String mService) { - this.mService = mService; - return this; - } - - public Builder setTrigger(@NonNull JobTrigger mTrigger) { - this.mTrigger = mTrigger; - return this; - } - - public Builder setRecurring(boolean mRecurring) { - this.mRecurring = mRecurring; - return this; - } - - public Builder setLifetime(@Lifetime.LifetimeConstant int mLifetime) { - this.mLifetime = mLifetime; - return this; - } - - public Builder setConstraints(@JobConstraint @NonNull int[] mConstraints) { - this.mConstraints = mConstraints; - return this; - } - - public Builder addExtras(@NonNull Bundle bundle) { - if (bundle != null) { - mExtras.putAll(bundle); - } - return this; - } - - public Builder setRetryStrategy(RetryStrategy mRetryStrategy) { - this.mRetryStrategy = mRetryStrategy; - return this; - } - - public Builder setReplaceCurrent(boolean mReplaceCurrent) { - this.mReplaceCurrent = mReplaceCurrent; - return this; - } - - public Builder setTriggerReason(TriggerReason triggerReason) { - this.mTriggerReason = triggerReason; - return this; - } - } - - /** - * @return true if the tag and the service of provided {@link JobInvocation} have the same - * values. - */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || !getClass().equals(o.getClass())) { - return false; - } - - JobInvocation jobInvocation = (JobInvocation) o; - - return mTag.equals(jobInvocation.mTag) - && mService.equals(jobInvocation.mService); - } - - @Override - public int hashCode() { - int result = mTag.hashCode(); - result = 31 * result + mService.hashCode(); - return result; - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobParameters.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobParameters.java deleted file mode 100644 index 24f0844e1..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobParameters.java +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import com.firebase.jobdispatcher.Constraint.JobConstraint; - -/** - * JobParameters represents anything that can describe itself in terms of Job components. - */ -public interface JobParameters { - - /** - * Returns the name of the backing JobService class. - */ - @NonNull - String getService(); - - /** - * Returns a string identifier for the Job. Used when cancelling Jobs and displaying debug - * messages. - */ - @NonNull - String getTag(); - - /** - * The Job's Trigger, which decides when the Job is ready to run. - */ - @NonNull - JobTrigger getTrigger(); - - /** - * The Job's lifetime; how long it should persist for. - */ - @Lifetime.LifetimeConstant - int getLifetime(); - - /** - * Whether the Job should repeat. - */ - boolean isRecurring(); - - /** - * The runtime constraints applied to this Job. A Job is not run until the trigger is activated - * and all the runtime constraints are satisfied. - */ - @JobConstraint - int[] getConstraints(); - - /** - * The optional set of user-supplied extras associated with this Job. - */ - @Nullable - Bundle getExtras(); - - /** - * The RetryStrategy for the Job. Used to determine how to handle failures. - */ - @NonNull - RetryStrategy getRetryStrategy(); - - /** - * Whether the Job should replace a pre-existing Job with the same tag. - */ - boolean shouldReplaceCurrent(); - - /** @return A {@link TriggerReason} that - if non null - describes why the job was triggered. */ - @Nullable - TriggerReason getTriggerReason(); -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobService.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobService.java deleted file mode 100644 index 41076e767..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobService.java +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.app.Service; -import android.content.Intent; -import android.content.res.Configuration; -import android.os.Binder; -import android.os.IBinder; -import android.os.Message; -import android.support.annotation.IntDef; -import android.support.annotation.MainThread; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; -import android.support.v4.util.SimpleArrayMap; -import android.util.Log; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Locale; - -/** - * JobService is the fundamental unit of work used in the JobDispatcher. - *

- * Users will need to override {@link #onStartJob(JobParameters)}, which is where any asynchronous - * execution should start. This method, like most lifecycle methods, runs on the main thread; you - * must offload execution to another thread (or {@link android.os.AsyncTask}, or - * {@link android.os.Handler}, or your favorite flavor of concurrency). - *

- * Once any asynchronous work is complete {@link #jobFinished(JobParameters, boolean)} should be - * called to inform the backing driver of the result. - *

- * Implementations should also override {@link #onStopJob(JobParameters)}, which will be called if - * the scheduling engine wishes to interrupt your work (most likely because the runtime constraints - * that are associated with the job in question are no longer met). - */ -public abstract class JobService extends Service { - /** - * Returned to indicate the job was executed successfully. If the job is not recurring (i.e. a - * one-off) it will be dequeued and forgotten. If it is recurring the trigger will be reset and - * the job will be requeued. - */ - public static final int RESULT_SUCCESS = 0; - - /** - * Returned to indicate the job encountered an error during execution and should be retried after - * a backoff period. - */ - public static final int RESULT_FAIL_RETRY = 1; - - /** - * Returned to indicate the job encountered an error during execution but should not be retried. - * If the job is not recurring (i.e. a one-off) it will be dequeued and forgotten. If it is - * recurring the trigger will be reset and the job will be requeued. - */ - public static final int RESULT_FAIL_NORETRY = 2; - - static final String TAG = "FJD.JobService"; - - @VisibleForTesting - static final String ACTION_EXECUTE = "com.firebase.jobdispatcher.ACTION_EXECUTE"; - - /** - * Correlates job tags (unique strings) with Messages, which are used to signal the completion - * of a job. - */ - private final SimpleArrayMap runningJobs = new SimpleArrayMap<>(1); - private LocalBinder binder = new LocalBinder(); - - /** - * The entry point to your Job. Implementations should offload work to another thread of - * execution as soon as possible because this runs on the main thread. If work was offloaded, - * call {@link JobService#jobFinished(JobParameters, boolean)} to notify the scheduling service - * that the work is completed. - *

- * In order to reschedule use {@link JobService#jobFinished(JobParameters, boolean)}. - * - * @return {@code true} if there is more work remaining in the worker thread, {@code false} if the job was completed. - */ - @MainThread - public abstract boolean onStartJob(JobParameters job); - - /** - * Called when the scheduling engine has decided to interrupt the execution of a running job, - * most likely because the runtime constraints associated with the job are no longer satisfied. - * The job must stop execution. - * - * @return true if the job should be retried - * @see com.firebase.jobdispatcher.JobInvocation.Builder#setRetryStrategy(RetryStrategy) - * @see RetryStrategy - */ - @MainThread - public abstract boolean onStopJob(JobParameters job); - - @MainThread - void start(JobParameters job, Message msg) { - synchronized (runningJobs) { - if (runningJobs.containsKey(job.getTag())) { - Log.w(TAG, String - .format(Locale.US, "Job with tag = %s was already running.", job.getTag())); - return; - } - runningJobs.put(job.getTag(), new JobCallback(msg)); - - boolean moreWork = onStartJob(job); - if (!moreWork) { - JobCallback callback = runningJobs.remove(job.getTag()); - if (callback != null) { - callback.sendResult(RESULT_SUCCESS); - } - } - } - } - - @MainThread - void stop(JobInvocation job) { - synchronized (runningJobs) { - JobCallback jobCallback = runningJobs.remove(job.getTag()); - - if (jobCallback == null) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Provided job has already been executed."); - } - return; - } - boolean shouldRetry = onStopJob(job); - jobCallback.sendResult(shouldRetry ? RESULT_FAIL_RETRY : RESULT_SUCCESS); - } - } - - /** - * Callback to inform the scheduling driver that you've finished executing. Can be called from - * any thread. When the system receives this message, it will release the wakelock being held. - * - * @param job - * @param needsReschedule - * whether the job should be rescheduled - * @see com.firebase.jobdispatcher.JobInvocation.Builder#setRetryStrategy(RetryStrategy) - */ - public final void jobFinished(@NonNull JobParameters job, boolean needsReschedule) { - if (job == null) { - Log.e(TAG, "jobFinished called with a null JobParameters"); - return; - } - - synchronized (runningJobs) { - JobCallback jobCallback = runningJobs.remove(job.getTag()); - - if (jobCallback != null) { - jobCallback.sendResult(needsReschedule ? RESULT_FAIL_RETRY : RESULT_SUCCESS); - } - } - } - - @Override - public final int onStartCommand(Intent intent, int flags, int startId) { - stopSelf(startId); - - return START_NOT_STICKY; - } - - @Nullable - @Override - public final IBinder onBind(Intent intent) { - return binder; - } - - @Override - public final boolean onUnbind(Intent intent) { - synchronized (runningJobs) { - for (int i = runningJobs.size() - 1; i >= 0; i--) { - JobCallback callback = runningJobs.get(runningJobs.keyAt(i)); - if (callback != null && callback.message != null) { - if (callback.message.obj instanceof JobParameters) { - callback.sendResult(onStopJob((JobParameters) callback.message.obj) - // returned true, would like to be rescheduled - ? RESULT_FAIL_RETRY - // returned false, but was interrupted so consider it a fail - : RESULT_FAIL_NORETRY); - } - } - } - } - - return super.onUnbind(intent); - } - - @Override - public final void onRebind(Intent intent) { - super.onRebind(intent); - } - - @Override - public final void onStart(Intent intent, int startId) { - } - - @Override - protected final void dump(FileDescriptor fd, PrintWriter writer, String[] args) { - super.dump(fd, writer, args); - } - - @Override - public final void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - } - - @Override - public final void onTaskRemoved(Intent rootIntent) { - super.onTaskRemoved(rootIntent); - } - - /** - * The result returned from a job execution. - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef({RESULT_SUCCESS, RESULT_FAIL_RETRY, RESULT_FAIL_NORETRY}) - public @interface JobResult { - } - - private final static class JobCallback { - public final Message message; - - private JobCallback(Message message) { - this.message = message; - } - - void sendResult(@JobResult int result) { - try { - if (message != null) { - message.arg1 = result; - message.sendToTarget(); - } - } catch (Exception ignored) {}//catch this freaking crash!!!! - } - } - - class LocalBinder extends Binder { - JobService getService() { - return JobService.this; - } - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobServiceConnection.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobServiceConnection.java deleted file mode 100644 index c96e9f687..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobServiceConnection.java +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static com.firebase.jobdispatcher.ExecutionDelegator.TAG; - -import android.content.ComponentName; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.os.Message; -import android.support.annotation.VisibleForTesting; -import android.util.Log; - -/** - * ServiceConnection for job execution. - */ -@VisibleForTesting -class JobServiceConnection implements ServiceConnection { - - private final JobInvocation jobInvocation; - // Should be sent only once. Can't be reused. - private final Message jobFinishedMessage; - private boolean wasMessageUsed = false; - - //Guarded by "this". Can be updated from main and binder threads. - private JobService.LocalBinder binder; - - JobServiceConnection(JobInvocation jobInvocation, Message jobFinishedMessage) { - this.jobFinishedMessage = jobFinishedMessage; - this.jobInvocation = jobInvocation; - this.jobFinishedMessage.obj = this.jobInvocation; - } - - @Override - public synchronized void onServiceConnected(ComponentName name, IBinder service) { - if (!(service instanceof JobService.LocalBinder)) { - Log.w(TAG, "Unknown service connected"); - return; - } - if (wasMessageUsed) { - Log.w(TAG, "onServiceConnected Duplicate calls. Ignored."); - return; - } else { - wasMessageUsed = true; - } - - binder = (JobService.LocalBinder) service; - - JobService jobService = binder.getService(); - - jobService.start(jobInvocation, jobFinishedMessage); - } - - @Override - public synchronized void onServiceDisconnected(ComponentName name) { - binder = null; - } - - synchronized boolean isBound() { - return binder != null; - } - - synchronized void onStop() { - if (isBound()) { - binder.getService().stop(jobInvocation); - } - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobTrigger.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobTrigger.java deleted file mode 100644 index b1c510c7f..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobTrigger.java +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import java.util.List; - -/** - * Contains all supported triggers. - */ -public class JobTrigger { - - /** - * ImmediateTrigger is a Trigger that's immediately available. The Job will be run as soon as - * the runtime constraints are satisfied. - */ - public static final class ImmediateTrigger extends JobTrigger { - /* package */ ImmediateTrigger() {} - } - - /** - * ExecutionWindow represents a Job trigger that becomes eligible once - * the current elapsed time exceeds the scheduled time + the {@code windowStart} - * value. The scheduler backend is encouraged to use the windowEnd value as a - * signal that the job should be run, but this is not an enforced behavior. - */ - public static final class ExecutionWindowTrigger extends JobTrigger { - private final int mWindowStart; - private final int mWindowEnd; - - /* package */ ExecutionWindowTrigger(int windowStart, int windowEnd) { - this.mWindowStart = windowStart; - this.mWindowEnd = windowEnd; - } - - public int getWindowStart() { - return mWindowStart; - } - - public int getWindowEnd() { - return mWindowEnd; - } - } - - /** A trigger that will be triggered on content update for any of provided uris. */ - public static final class ContentUriTrigger extends JobTrigger { - private final List uris; - - /* package */ ContentUriTrigger(List uris) { - this.uris = uris; - } - - public List getUris() { - return uris; - } - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobValidator.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobValidator.java deleted file mode 100644 index c97198f49..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/JobValidator.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.support.annotation.Nullable; -import java.util.List; - -/** - * A JobValidator is an object that knows how to validate Jobs and some of their composite - * components. - */ -public interface JobValidator { - /** - * Returns a List of error messages, or null if the JobParameters is - * valid. - */ - @Nullable - List validate(JobParameters job); - - /** - * Returns a List of error messages, or null if the Trigger is - * valid. - * @param trigger - */ - @Nullable - List validate(JobTrigger trigger); - - /** - * Returns a List of error messages, or null if the RetryStrategy - * is valid. - */ - @Nullable - List validate(RetryStrategy retryStrategy); - -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Lifetime.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Lifetime.java deleted file mode 100644 index 456bc221c..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Lifetime.java +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.support.annotation.IntDef; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Lifetime represents how long a Job should last. - */ -public final class Lifetime { - /** - * The Job should be preserved until the next boot. This is the default. - */ - public final static int UNTIL_NEXT_BOOT = 1; - - /** - * The Job should be preserved "forever." - */ - public final static int FOREVER = 2; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({FOREVER, UNTIL_NEXT_BOOT}) - @interface LifetimeConstant {} -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/ObservedUri.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/ObservedUri.java deleted file mode 100644 index b22c15e01..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/ObservedUri.java +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.net.Uri; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** Represents a single observed URI and any associated flags. */ -public final class ObservedUri { - - private final Uri uri; - - private final int flags; - - /** Flag enforcement. */ - @IntDef(flag = true, value = Flags.FLAG_NOTIFY_FOR_DESCENDANTS) - @Retention(RetentionPolicy.SOURCE) - public @interface Flags { - - /** - * Triggers if any descendants of the given URI change. Corresponds to the {@code - * notifyForDescendants} of {@link android.content.ContentResolver#registerContentObserver}. - */ - int FLAG_NOTIFY_FOR_DESCENDANTS = 1 << 0; - } - - /** - * Create a new ObservedUri. - * - * @param uri The URI to observe. - * @param flags Any {@link Flags} associated with the URI. - */ - public ObservedUri(@NonNull Uri uri, @Flags int flags) { - if (uri == null) { - throw new IllegalArgumentException("URI must not be null."); - } - this.uri = uri; - this.flags = flags; - } - - public Uri getUri() { - return uri; - } - - public int getFlags() { - return flags; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ObservedUri)) { - return false; - } - - ObservedUri otherUri = (ObservedUri) o; - return flags == otherUri.flags && uri.equals(otherUri.uri); - } - - @Override - public int hashCode() { - return uri.hashCode() ^ flags; - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/RetryStrategy.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/RetryStrategy.java deleted file mode 100644 index 7d9b31901..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/RetryStrategy.java +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.support.annotation.IntDef; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * RetryStrategy represents an approach to handling job execution failures. Jobs will have a - * time-based backoff enforced, based on the chosen policy (one of {@code RETRY_POLICY_EXPONENTIAL} - * or {@code RETRY_POLICY_LINEAR}. - */ -public final class RetryStrategy { - /** - * Increase the backoff time exponentially. - *

- * Calculated using {@code initial_backoff * 2 ^ (num_failures - 1)}. - */ - public final static int RETRY_POLICY_EXPONENTIAL = 1; - - /** - * Increase the backoff time linearly. - *

- * Calculated using {@code initial_backoff * num_failures}. - */ - public final static int RETRY_POLICY_LINEAR = 2; - - /** - * Expected schedule is: [30s, 60s, 120s, 240s, ..., 3600s] - */ - public final static RetryStrategy DEFAULT_EXPONENTIAL = - new RetryStrategy(RETRY_POLICY_EXPONENTIAL, 30, 3600); - - /** - * Expected schedule is: [30s, 60s, 90s, 120s, ..., 3600s] - */ - public final static RetryStrategy DEFAULT_LINEAR = - new RetryStrategy(RETRY_POLICY_LINEAR, 30, 3600); - - @RetryPolicy - private final int mPolicy; - private final int mInitialBackoff; - private final int mMaximumBackoff; - - /* package */ RetryStrategy(@RetryPolicy int policy, int initialBackoff, int maximumBackoff) { - mPolicy = policy; - mInitialBackoff = initialBackoff; - mMaximumBackoff = maximumBackoff; - } - - /** - * Returns the backoff policy in place. - */ - @RetryPolicy - public int getPolicy() { - return mPolicy; - } - - /** - * Returns the initial backoff (i.e. when # of failures == 1), in seconds. - */ - public int getInitialBackoff() { - return mInitialBackoff; - } - - /** - * Returns the maximum backoff duration in seconds. - */ - public int getMaximumBackoff() { - return mMaximumBackoff; - } - - @IntDef({RETRY_POLICY_LINEAR, RETRY_POLICY_EXPONENTIAL}) - @Retention(RetentionPolicy.SOURCE) - public @interface RetryPolicy { - } - - /* package */ final static class Builder { - private final ValidationEnforcer mValidator; - - Builder(ValidationEnforcer validator) { - mValidator = validator; - } - - public RetryStrategy build(@RetryPolicy int policy, int initialBackoff, int maxBackoff) { - RetryStrategy rs = new RetryStrategy(policy, initialBackoff, maxBackoff); - mValidator.ensureValid(rs); - return rs; - } - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/SimpleJobService.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/SimpleJobService.java deleted file mode 100644 index 8cfbe5f4c..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/SimpleJobService.java +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.os.AsyncTask; -import android.support.annotation.CallSuper; -import android.support.v4.util.SimpleArrayMap; - -/** - * SimpleJobService provides a simple way of doing background work in a JobService. - * - * Users should override onRunJob and return one of the {@link JobResult} ints. - */ -public abstract class SimpleJobService extends JobService { - private final SimpleArrayMap runningJobs = - new SimpleArrayMap<>(); - - @CallSuper - @Override - public boolean onStartJob(JobParameters job) { - AsyncJobTask async = new AsyncJobTask(this, job); - - synchronized (runningJobs) { - runningJobs.put(job, async); - } - - async.execute(); - - return true; // more work to do - } - - @CallSuper - @Override - public boolean onStopJob(JobParameters job) { - synchronized (runningJobs) { - AsyncJobTask async = runningJobs.remove(job); - if (async != null) { - async.cancel(true); - return true; - } - } - - return false; - } - - private void onJobFinished(JobParameters jobParameters, boolean b) { - synchronized (runningJobs) { - runningJobs.remove(jobParameters); - } - - jobFinished(jobParameters, b); - } - - @JobResult - public abstract int onRunJob(JobParameters job); - - private static class AsyncJobTask extends AsyncTask { - private final SimpleJobService jobService; - private final JobParameters jobParameters; - - private AsyncJobTask(SimpleJobService jobService, JobParameters jobParameters) { - this.jobService = jobService; - this.jobParameters = jobParameters; - } - - @Override - protected Integer doInBackground(Void... params) { - return jobService.onRunJob(jobParameters); - } - - @Override - protected void onPostExecute(Integer integer) { - jobService.onJobFinished(jobParameters, integer == JobService.RESULT_FAIL_RETRY); - } - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Trigger.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Trigger.java deleted file mode 100644 index 3e01ae9c5..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/Trigger.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.support.annotation.NonNull; -import java.util.List; - -/** - * Generally, a Trigger is an object that can answer the question, "is this job ready to run?" - *

- * More specifically, a Trigger is an opaque, abstract class used to root the type hierarchy. - */ -public final class Trigger { - - /** - * Immediate is a Trigger that's immediately available. The Job will be run as soon as the - * runtime constraints are satisfied. - *

- * It is invalid to schedule an Immediate with a recurring Job. - */ - public final static JobTrigger.ImmediateTrigger NOW = new JobTrigger.ImmediateTrigger(); - - /** - * Creates a new ExecutionWindow based on the provided time interval. - * - * @param windowStart The earliest time (in seconds) the job should be - * considered eligible to run. Calculated from when the - * job was scheduled (for new jobs) or last run (for - * recurring jobs). - * @param windowEnd The latest time (in seconds) the job should be run in - * an ideal world. Calculated in the same way as - * {@code windowStart}. - * @throws IllegalArgumentException if the provided parameters are too - * restrictive. - */ - public static JobTrigger.ExecutionWindowTrigger executionWindow(int windowStart, int windowEnd) { - if (windowStart < 0) { - throw new IllegalArgumentException("Window start can't be less than 0"); - } else if (windowEnd < windowStart) { - throw new IllegalArgumentException("Window end can't be less than window start"); - } - - return new JobTrigger.ExecutionWindowTrigger(windowStart, windowEnd); - } - - /** - * Creates a new ContentUriTrigger based on the provided list of {@link ObservedUri}. - * - * @param uris The list of URIs to observe. The trigger will be available if a piece of content, - * corresponding to any of provided URIs, is updated. - * @throws IllegalArgumentException if provided list of URIs is null or empty. - */ - public static JobTrigger.ContentUriTrigger contentUriTrigger(@NonNull List uris) { - if (uris == null || uris.isEmpty()) { - throw new IllegalArgumentException("Uris must not be null or empty."); - } - return new JobTrigger.ContentUriTrigger(uris); - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/TriggerReason.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/TriggerReason.java deleted file mode 100644 index 00fc053b8..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/TriggerReason.java +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.net.Uri; -import java.util.List; - -/** The class contains a summary of the events which caused the job to be executed. */ -public class TriggerReason { - private final List mTriggeredContentUris; - - TriggerReason(List mTriggeredContentUris) { - this.mTriggeredContentUris = mTriggeredContentUris; - } - - public List getTriggeredContentUris() { - return mTriggeredContentUris; - } -} diff --git a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/ValidationEnforcer.java b/jobdispatcher/src/main/java/com/firebase/jobdispatcher/ValidationEnforcer.java deleted file mode 100644 index d9cfbe02f..000000000 --- a/jobdispatcher/src/main/java/com/firebase/jobdispatcher/ValidationEnforcer.java +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.TextUtils; -import java.util.List; - -/** - * Wraps a JobValidator and provides helpful validation utilities. - */ -public class ValidationEnforcer implements JobValidator { - private final JobValidator mValidator; - - public ValidationEnforcer(JobValidator validator) { - mValidator = validator; - } - - /** - * {@inheritDoc} - */ - @Nullable - @Override - public List validate(JobParameters job) { - return mValidator.validate(job); - } - - /** - * {@inheritDoc} - * @param trigger - */ - @Nullable - @Override - public List validate(JobTrigger trigger) { - return mValidator.validate(trigger); - } - - /** - * {@inheritDoc} - */ - @Nullable - @Override - public List validate(RetryStrategy retryStrategy) { - return mValidator.validate(retryStrategy); - } - - /** - * Indicates whether the provided JobParameters is valid. - */ - public final boolean isValid(JobParameters job) { - return validate(job) == null; - } - - /** - * Indicates whether the provided JobTrigger is valid. - */ - public final boolean isValid(JobTrigger trigger) { - return validate(trigger) == null; - } - - /** - * Indicates whether the provided RetryStrategy is valid. - */ - public final boolean isValid(RetryStrategy retryStrategy) { - return validate(retryStrategy) == null; - } - - /** - * Throws a RuntimeException if the provided JobParameters is invalid. - * - * @throws ValidationException - */ - public final void ensureValid(JobParameters job) { - ensureNoErrors(validate(job)); - } - - /** - * Throws a RuntimeException if the provided JobTrigger is invalid. - * - * @throws ValidationException - */ - public final void ensureValid(JobTrigger trigger) { - ensureNoErrors(validate(trigger)); - } - - /** - * Throws a RuntimeException if the provided RetryStrategy is - * invalid. - * - * @throws ValidationException - */ - public final void ensureValid(RetryStrategy retryStrategy) { - ensureNoErrors(validate(retryStrategy)); - } - - private void ensureNoErrors(List errors) { - if (errors != null) { - throw new ValidationException("JobParameters is invalid", errors); - } - } - - /** - * An Exception thrown when a validation error is encountered. - */ - public final static class ValidationException extends RuntimeException { - private final List mErrors; - - public ValidationException(String msg, @NonNull List errors) { - super(msg + ": " + TextUtils.join("\n - ", errors)); - mErrors = errors; - } - - public List getErrors() { - return mErrors; - } - } -} diff --git a/jobdispatcher/src/test/java/android/net/http/AndroidHttpClient.java b/jobdispatcher/src/test/java/android/net/http/AndroidHttpClient.java deleted file mode 100644 index 0720a56f5..000000000 --- a/jobdispatcher/src/test/java/android/net/http/AndroidHttpClient.java +++ /dev/null @@ -1,10 +0,0 @@ -package android.net.http; - -/** - * Robolectric requires this class be available in the classpath, otherwise {@link - * org.robolectric.Shadows#shadowOf(android.os.Looper)} fails. We don't use AndroidHttpClient, so - * include a stub to make Robolectric happy. - * - * @see https://github.com/robolectric/robolectric/issues/1862 - */ -public class AndroidHttpClient {} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ConstraintTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ConstraintTest.java deleted file mode 100644 index 808d7a3c6..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ConstraintTest.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static org.junit.Assert.assertEquals; - -import android.text.TextUtils; -import java.util.Arrays; -import java.util.List; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23) -public class ConstraintTest { - - /** - * Just to get 100% coverage. - */ - @Test - public void testPrivateConstructor() throws Exception { - TestUtil.assertHasSinglePrivateConstructor(Constraint.class); - } - - @Test - public void testCompactAndUnCompact() { - for (List combo : TestUtil.getAllConstraintCombinations()) { - int[] input = TestUtil.toIntArray(combo); - Arrays.sort(input); - - int[] output = Constraint.uncompact(Constraint.compact(input)); - Arrays.sort(output); - - for (int i = 0; i < combo.size(); i++) { - assertEquals("Combination = " + TextUtils.join(", ", combo), - input[i], - output[i]); - } - - assertEquals("Expected length of two arrays to be the same", - input.length, - output.length); - } - } - - @Test - public void compactNull() { - assertEquals(0, Constraint.compact(null)); - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ContentUriTriggerTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ContentUriTriggerTest.java deleted file mode 100644 index 5ea200af0..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ContentUriTriggerTest.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static junit.framework.Assert.assertEquals; - -import android.provider.ContactsContract; -import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -/** Test for {@link ContentUriTrigger}. */ -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 21) -public class ContentUriTriggerTest { - - @Test(expected = IllegalArgumentException.class) - public void constrains_null() throws Exception { - Trigger.contentUriTrigger(null); - } - - @Test(expected = IllegalArgumentException.class) - public void constrains_emptyList() throws Exception { - Trigger.contentUriTrigger(Collections.emptyList()); - } - - @Test - public void constrains_valid() throws Exception { - List uris = Arrays.asList(new ObservedUri(ContactsContract.AUTHORITY_URI, 0)); - ContentUriTrigger uriTrigger = Trigger.contentUriTrigger(uris); - assertEquals(uris, uriTrigger.getUris()); - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/DefaultJobValidatorTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/DefaultJobValidatorTest.java deleted file mode 100644 index 2d5264175..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/DefaultJobValidatorTest.java +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static java.util.Collections.singletonList; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import android.content.Context; -import android.provider.ContactsContract; -import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger; -import com.firebase.jobdispatcher.ObservedUri.Flags; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23) -public class DefaultJobValidatorTest { - - @Mock - private Context mMockContext; - - private DefaultJobValidator mValidator; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mValidator = new DefaultJobValidator(mMockContext); - } - - @SuppressWarnings("WrongConstant") - @Test - public void testValidate_retryStrategy() throws Exception { - Map> testCases = new HashMap<>(); - testCases.put( - new RetryStrategy(0 /* bad policy */, 30, 3600), - singletonList("Unknown retry policy provided")); - testCases.put( - new RetryStrategy(RetryStrategy.RETRY_POLICY_LINEAR, 15, 3600), - singletonList("Initial backoff must be at least 30s")); - testCases.put( - new RetryStrategy(RetryStrategy.RETRY_POLICY_EXPONENTIAL, 15, 3600), - singletonList("Initial backoff must be at least 30s")); - testCases.put( - new RetryStrategy(RetryStrategy.RETRY_POLICY_LINEAR, 30, 60), - singletonList("Maximum backoff must be greater than 300s (5 minutes)")); - testCases.put( - new RetryStrategy(RetryStrategy.RETRY_POLICY_EXPONENTIAL, 30, 60), - singletonList("Maximum backoff must be greater than 300s (5 minutes)")); - testCases.put( - new RetryStrategy(RetryStrategy.RETRY_POLICY_LINEAR, 301, 300), - singletonList("Maximum backoff must be greater than or equal to initial backoff")); - testCases.put( - new RetryStrategy(RetryStrategy.RETRY_POLICY_EXPONENTIAL, 301, 300), - singletonList("Maximum backoff must be greater than or equal to initial backoff")); - - for (Entry> testCase : testCases.entrySet()) { - List validationErrors = mValidator.validate(testCase.getKey()); - assertNotNull("Expected validation errors, but got null", validationErrors); - - for (String expected : testCase.getValue()) { - assertTrue( - "Expected validation errors to contain \"" + expected + "\"", - validationErrors.contains(expected)); - } - } - } - - @Test - public void testValidate_trigger() throws Exception { - Map testCases = new HashMap<>(); - - testCases.put(Trigger.NOW, null); - testCases.put(Trigger.executionWindow(0, 100), null); - ContentUriTrigger contentUriTrigger = - Trigger.contentUriTrigger( - Arrays.asList( - new ObservedUri( - ContactsContract.AUTHORITY_URI, Flags.FLAG_NOTIFY_FOR_DESCENDANTS))); - testCases.put(contentUriTrigger, null); - - for (Entry testCase : testCases.entrySet()) { - List validationErrors = mValidator.validate(testCase.getKey()); - if (testCase.getValue() == null) { - assertNull("Expected no validation errors for trigger", validationErrors); - } else { - assertTrue( - "Expected validation errors to contain \"" + testCase.getValue() + "\"", - validationErrors.contains(testCase.getValue())); - } - } - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ExecutionDelegatorTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ExecutionDelegatorTest.java deleted file mode 100644 index a523c4654..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ExecutionDelegatorTest.java +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static com.firebase.jobdispatcher.TestUtil.getContentUriTrigger; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.support.annotation.NonNull; -import com.firebase.jobdispatcher.JobService.JobResult; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -@SuppressWarnings("WrongConstant") -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 21) -public class ExecutionDelegatorTest { - - private Context mMockContext; - private TestJobReceiver mReceiver; - private ExecutionDelegator mExecutionDelegator; - - @Before - public void setUp() { - mMockContext = spy(RuntimeEnvironment.application); - doReturn("com.example.foo").when(mMockContext).getPackageName(); - - mReceiver = new TestJobReceiver(); - mExecutionDelegator = new ExecutionDelegator(mMockContext, mReceiver); - } - - @Test - public void testExecuteJob_sendsBroadcastWithJobAndMessage() throws Exception { - for (JobInvocation input : TestUtil.getJobInvocationCombinations()) { - verifyExecuteJob(input); - } - } - - private void verifyExecuteJob(JobInvocation input) throws Exception { - reset(mMockContext); - mReceiver.lastResult = -1; - - mReceiver.setLatch(new CountDownLatch(1)); - - mExecutionDelegator.executeJob(input); - - final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - final ArgumentCaptor connCaptor = - ArgumentCaptor.forClass(ServiceConnection.class); - verify(mMockContext).bindService(intentCaptor.capture(), connCaptor.capture(), anyInt()); - - final Intent result = intentCaptor.getValue(); - // verify the intent was sent to the right place - assertEquals(input.getService(), result.getComponent().getClassName()); - assertEquals(JobService.ACTION_EXECUTE, result.getAction()); - - final ServiceConnection connection = connCaptor.getValue(); - - ComponentName cname = mock(ComponentName.class); - JobService.LocalBinder mockLocalBinder = mock(JobService.LocalBinder.class); - final JobParameters[] out = new JobParameters[1]; - - JobService mockJobService = new JobService() { - @Override - public boolean onStartJob(JobParameters job) { - out[0] = job; - return false; - } - - @Override - public boolean onStopJob(JobParameters job) { - return false; - } - }; - - when(mockLocalBinder.getService()).thenReturn(mockJobService); - - connection.onServiceConnected(cname, mockLocalBinder); - - TestUtil.assertJobsEqual(input, out[0]); - - // make sure the countdownlatch was decremented - assertTrue(mReceiver.mLatch.await(1, TimeUnit.SECONDS)); - - // verify the lastResult was set correctly - assertEquals(JobService.RESULT_SUCCESS, mReceiver.lastResult); - } - - @Test - public void testExecuteJob_handlesNull() { - assertFalse("Expected calling triggerExecution on null to fail and return false", - mExecutionDelegator.executeJob(null)); - } - - @Test - public void testHandleMessage_doesntCrashOnBadJobData() { - JobInvocation j = new JobInvocation.Builder() - .setService(TestJobService.class.getName()) - .setTag("tag") - .setTrigger(Trigger.NOW) - .build(); - - mExecutionDelegator.executeJob(j); - - ArgumentCaptor intentCaptor = - ArgumentCaptor.forClass(Intent.class); - ArgumentCaptor connCaptor = - ArgumentCaptor.forClass(ServiceConnection.class); - - //noinspection WrongConstant - verify(mMockContext).bindService(intentCaptor.capture(), connCaptor.capture(), anyInt()); - - Intent executeReq = intentCaptor.getValue(); - assertEquals(JobService.ACTION_EXECUTE, executeReq.getAction()); - } - - @Test - public void onStop_mock() throws InterruptedException { - JobInvocation job = new JobInvocation.Builder() - .setTag("TAG") - .setTrigger(getContentUriTrigger()) - .setService(TestJobService.class.getName()) - .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) - .build(); - - reset(mMockContext); - mReceiver.lastResult = -1; - - mExecutionDelegator.executeJob(job); - - final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - final ArgumentCaptor connCaptor = - ArgumentCaptor.forClass(ServiceConnection.class); - verify(mMockContext).bindService(intentCaptor.capture(), connCaptor.capture(), anyInt()); - - final Intent result = intentCaptor.getValue(); - // verify the intent was sent to the right place - assertEquals(job.getService(), result.getComponent().getClassName()); - assertEquals(JobService.ACTION_EXECUTE, result.getAction()); - - final JobParameters[] out = new JobParameters[2]; - - JobService mockJobService = new JobService() { - @Override - public boolean onStartJob(JobParameters job) { - out[0] = job; - return true; - } - - @Override - public boolean onStopJob(JobParameters job) { - out[1] = job; - return false; - } - }; - - JobService.LocalBinder mockLocalBinder = mock(JobService.LocalBinder.class); - when(mockLocalBinder.getService()).thenReturn(mockJobService); - - ComponentName componentName = mock(ComponentName.class); - final ServiceConnection connection = connCaptor.getValue(); - connection.onServiceConnected(componentName, mockLocalBinder); - - mExecutionDelegator.stopJob(job); - - TestUtil.assertJobsEqual(job, out[0]); - TestUtil.assertJobsEqual(job, out[1]); - } - - private final static class TestJobReceiver implements ExecutionDelegator.JobFinishedCallback { - int lastResult; - - private CountDownLatch mLatch; - - @Override - public void onJobFinished(@NonNull JobInvocation js, @JobResult int result) { - lastResult = result; - - if (mLatch != null) { - mLatch.countDown(); - } - } - - /** - * Convenience method for tests. - */ - public void setLatch(CountDownLatch latch) { - mLatch = latch; - } - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ExecutionWindowTriggerTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ExecutionWindowTriggerTest.java deleted file mode 100644 index 6dfe85774..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ExecutionWindowTriggerTest.java +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static org.junit.Assert.assertEquals; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23) -public class ExecutionWindowTriggerTest { - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Test - public void testNewInstance_withValidWindow() throws Exception { - JobTrigger.ExecutionWindowTrigger trigger = Trigger.executionWindow(0, 60); - - assertEquals(0, trigger.getWindowStart()); - assertEquals(60, trigger.getWindowEnd()); - } - - @Test - public void testNewInstance_withNegativeStart() throws Exception { - expectedException.expect(IllegalArgumentException.class); - - Trigger.executionWindow(-10, 60); - } - - @Test - public void testNewInstance_withNegativeEnd() throws Exception { - expectedException.expect(IllegalArgumentException.class); - - Trigger.executionWindow(0, -1); - } - - @Test - public void testNewInstance_withReversedValues() throws Exception { - expectedException.expect(IllegalArgumentException.class); - - Trigger.executionWindow(60, 0); - } - - @Test - public void testNewInstance_withTooSmallWindow_now() throws Exception { - expectedException.expect(IllegalArgumentException.class); - - Trigger.executionWindow(60, 59); - } - - @Test - public void testNewInstance_withTooSmallWindow_inFuture() throws Exception { - expectedException.expect(IllegalArgumentException.class); - - Trigger.executionWindow(200, 100); - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ExtendedShadowParcel.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ExtendedShadowParcel.java deleted file mode 100644 index e5ecf1042..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ExtendedShadowParcel.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.firebase.jobdispatcher; - -import android.os.IBinder; -import android.os.Parcel; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.RealObject; -import org.robolectric.shadows.ShadowParcel; - -/** - * ShadowParcel doesn't correctly handle {@link Parcel#writeStrongBinder(IBinder)} or {@link - * Parcel#readStrongBinder()}, so we shim a simple implementation that uses an in-memory map to read - * and write Binder objects. - */ -@Implements(Parcel.class) -public class ExtendedShadowParcel extends ShadowParcel { - @RealObject private Parcel realObject; - - // Map each IBinder to an integer, and use the super's int-writing capability to fake Binder - // read/writes. - private final AtomicInteger nextBinderId = new AtomicInteger(1); - private final Map binderMap = - Collections.synchronizedMap(new HashMap()); - - @Implementation - public void writeStrongBinder(IBinder binder) { - int id = nextBinderId.getAndIncrement(); - binderMap.put(id, binder); - realObject.writeInt(id); - } - - @Implementation - public IBinder readStrongBinder() { - return binderMap.get(realObject.readInt()); - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/FirebaseJobDispatcherTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/FirebaseJobDispatcherTest.java deleted file mode 100644 index dd0b22ee6..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/FirebaseJobDispatcherTest.java +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.firebase.jobdispatcher.FirebaseJobDispatcher.ScheduleFailedException; -import java.util.Arrays; -import java.util.List; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23) -public class FirebaseJobDispatcherTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Mock - private Driver mDriver; - - @Mock - private JobValidator mValidator; - - private FirebaseJobDispatcher mDispatcher; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - when(mDriver.getValidator()).thenReturn(mValidator); - - mDispatcher = new FirebaseJobDispatcher(mDriver); - setDriverAvailability(true); - } - - @Test - public void testSchedule_passThrough() throws Exception { - final int[] possibleResults = { - FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS, - FirebaseJobDispatcher.SCHEDULE_RESULT_NO_DRIVER_AVAILABLE, - FirebaseJobDispatcher.SCHEDULE_RESULT_BAD_SERVICE, - FirebaseJobDispatcher.SCHEDULE_RESULT_UNKNOWN_ERROR, - FirebaseJobDispatcher.SCHEDULE_RESULT_UNSUPPORTED_TRIGGER}; - - for (int result : possibleResults) { - when(mDriver.schedule(null)).thenReturn(result); - assertEquals(result, mDispatcher.schedule(null)); - } - - verify(mDriver, times(possibleResults.length)).schedule(null); - } - - @Test - public void testSchedule_unavailable() throws Exception { - setDriverAvailability(false); - assertEquals( - FirebaseJobDispatcher.SCHEDULE_RESULT_NO_DRIVER_AVAILABLE, - mDispatcher.schedule(null)); - verify(mDriver, never()).schedule(null); - } - - @Test - public void testCancelJob() throws Exception { - final String tag = "foo"; - - // simulate success - when(mDriver.cancel(tag)) - .thenReturn(FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS); - - assertEquals( - "Expected dispatcher to pass the result of Driver#cancel(String, Class) through", - FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS, - mDispatcher.cancel(tag)); - - // verify the driver was indeed called - verify(mDriver).cancel(tag); - } - - @Test - public void testCancelJob_unavailable() throws Exception { - setDriverAvailability(false); // driver is unavailable - - assertEquals( - FirebaseJobDispatcher.CANCEL_RESULT_NO_DRIVER_AVAILABLE, - mDispatcher.cancel("foo")); - - // verify the driver was never even consulted - verify(mDriver, never()).cancel("foo"); - } - - @Test - public void testCancelAllJobs() throws Exception { - when(mDriver.cancelAll()).thenReturn(FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS); - - assertEquals(FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS, mDispatcher.cancelAll()); - verify(mDriver).cancelAll(); - } - - @Test - public void testCancelAllJobs_unavailable() throws Exception { - setDriverAvailability(false); // driver is unavailable - - assertEquals( - FirebaseJobDispatcher.CANCEL_RESULT_NO_DRIVER_AVAILABLE, - mDispatcher.cancelAll()); - - verify(mDriver, never()).cancelAll(); - } - - @Test - public void testMustSchedule_success() throws Exception { - when(mDriver.schedule(null)).thenReturn(FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS); - - /* assert no exception is thrown */ - mDispatcher.mustSchedule(null); - } - - @Test - public void testMustSchedule_unavailable() throws Exception { - setDriverAvailability(false); // driver is unavailable - expectedException.expect(FirebaseJobDispatcher.ScheduleFailedException.class); - - mDispatcher.mustSchedule(null); - } - - @Test - public void testMustSchedule_failure() throws Exception { - final int[] possibleErrors = { - FirebaseJobDispatcher.SCHEDULE_RESULT_NO_DRIVER_AVAILABLE, - FirebaseJobDispatcher.SCHEDULE_RESULT_BAD_SERVICE, - FirebaseJobDispatcher.SCHEDULE_RESULT_UNKNOWN_ERROR, - FirebaseJobDispatcher.SCHEDULE_RESULT_UNSUPPORTED_TRIGGER}; - - for (int scheduleError : possibleErrors) { - when(mDriver.schedule(null)).thenReturn(scheduleError); - - try { - mDispatcher.mustSchedule(null); - - fail("Expected mustSchedule() with error code " + scheduleError + " to fail"); - } catch (ScheduleFailedException expected) { /* expected */ } - } - - verify(mDriver, times(possibleErrors.length)).schedule(null); - } - - @Test - public void testNewRetryStrategyBuilder() { - // custom validator that only approves strategies where initialbackoff == 30s - when(mValidator.validate(any(RetryStrategy.class))).thenAnswer(new Answer>() { - @Override - public List answer(InvocationOnMock invocation) throws Throwable { - RetryStrategy rs = (RetryStrategy) invocation.getArguments()[0]; - // only succeed if initialBackoff == 30s - return rs.getInitialBackoff() == 30 ? null : Arrays.asList("foo", "bar"); - } - }); - - try { - mDispatcher.newRetryStrategy(RetryStrategy.RETRY_POLICY_EXPONENTIAL, 0, 30); - fail("Expected initial backoff != 30s to fail using custom validator"); - } catch (Exception unused) { /* unused */ } - - try { - mDispatcher.newRetryStrategy(RetryStrategy.RETRY_POLICY_EXPONENTIAL, 30, 30); - } catch (Exception unused) { - fail("Expected initial backoff == 30s not to fail using custom validator"); - } - } - - public void setDriverAvailability(boolean driverAvailability) { - when(mDriver.isAvailable()).thenReturn(driverAvailability); - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayCallbackExtractorTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayCallbackExtractorTest.java deleted file mode 100644 index f65918501..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayCallbackExtractorTest.java +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -import android.os.Bundle; -import android.os.IBinder; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Pair; -import com.firebase.jobdispatcher.TestUtil.InspectableBinder; -import com.firebase.jobdispatcher.TestUtil.TransactionArguments; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Config( - constants = BuildConfig.class, - manifest = Config.NONE, - sdk = 23, - shadows = {ExtendedShadowParcel.class} -) -public final class GooglePlayCallbackExtractorTest { - @Mock - private IBinder mBinder; - - private GooglePlayCallbackExtractor mExtractor; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mExtractor = new GooglePlayCallbackExtractor(); - } - - @Test - public void testExtractCallback_nullBundle() { - assertNull(mExtractor.extractCallback(null)); - } - - @Test - public void testExtractCallback_nullParcelable() { - Bundle emptyBundle = new Bundle(); - assertNull(extractCallback(emptyBundle)); - } - - @Test - public void testExtractCallback_badParcelable() { - Bundle misconfiguredBundle = new Bundle(); - misconfiguredBundle.putParcelable("callback", new BadParcelable(1)); - - assertNull(extractCallback(misconfiguredBundle)); - } - - @Test - public void testExtractCallback_goodParcelable() { - InspectableBinder binder = new InspectableBinder(); - Bundle validBundle = new Bundle(); - validBundle.putParcelable("callback", binder.toPendingCallback()); - - Pair extraction = extractCallback(validBundle); - assertNotNull(extraction); - assertEquals("should have stripped the 'callback' entry from the extracted bundle", - 0, extraction.second.keySet().size()); - extraction.first.jobFinished(JobService.RESULT_SUCCESS); - - // Check our homemade Binder is doing the right things: - TransactionArguments args = binder.getArguments().get(0); - // Should have set the transaction code: - assertEquals("transaction code", IBinder.FIRST_CALL_TRANSACTION + 1, args.code); - - // strong mode bit - args.data.readInt(); - // interface token - assertEquals("com.google.android.gms.gcm.INetworkTaskCallback", args.data.readString()); - // result - assertEquals("result", JobService.RESULT_SUCCESS, args.data.readInt()); - } - - @Test - public void testExtractCallback_extraMapValues() { - Bundle validBundle = new Bundle(); - validBundle.putString("foo", "bar"); - validBundle.putInt("bar", 3); - validBundle.putParcelable("parcelable", new Bundle()); - validBundle.putParcelable("callback", new InspectableBinder().toPendingCallback()); - - Pair extraction = extractCallback(validBundle); - assertNotNull(extraction); - assertEquals("should have stripped the 'callback' entry from the extracted bundle", - 3, extraction.second.keySet().size()); - } - - private Pair extractCallback(Bundle bundle) { - return mExtractor.extractCallback(bundle); - } - - private static final class BadParcelable implements Parcelable { - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public BadParcelable createFromParcel(Parcel in) { - return new BadParcelable(in); - } - - @Override - public BadParcelable[] newArray(int size) { - return new BadParcelable[size]; - } - }; - private final int mNum; - - public BadParcelable(int i) { - mNum = i; - } - - private BadParcelable(Parcel in) { - mNum = in.readInt(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dst, int flags) { - dst.writeInt(mNum); - } - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayDriverTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayDriverTest.java deleted file mode 100644 index 822a8f1d6..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayDriverTest.java +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.os.Parcelable; -import android.support.annotation.NonNull; -import android.text.TextUtils; -import java.util.Arrays; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23) -public class GooglePlayDriverTest { - @Mock - public Context mMockContext; - - private TestJobDriver mDriver; - private FirebaseJobDispatcher mDispatcher; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mDriver = new TestJobDriver(new GooglePlayDriver(mMockContext)); - mDispatcher = new FirebaseJobDispatcher(mDriver); - - when(mMockContext.getPackageName()).thenReturn("foo.bar.whatever"); - } - - @Test - public void testSchedule_failsWhenPlayServicesIsUnavailable() throws Exception { - markBackendUnavailable(); - mockPackageManagerInfo(); - - Job job = null; - try { - job = mDispatcher.newJobBuilder() - .setService(TestJobService.class) - .setTag("foobar") - .setConstraints(Constraint.DEVICE_CHARGING) - .setTrigger(Trigger.executionWindow(0, 60)) - .build(); - } catch (ValidationEnforcer.ValidationException ve) { - fail(TextUtils.join("\n", ve.getErrors())); - } - - assertEquals("Expected schedule() request to fail when backend is unavailable", - FirebaseJobDispatcher.SCHEDULE_RESULT_NO_DRIVER_AVAILABLE, - mDispatcher.schedule(job)); - } - - @Test - public void testCancelJobs_backendUnavailable() throws Exception { - markBackendUnavailable(); - - assertEquals("Expected cancelAll() request to fail when backend is unavailable", - FirebaseJobDispatcher.CANCEL_RESULT_NO_DRIVER_AVAILABLE, - mDispatcher.cancelAll()); - } - - @Test - public void testSchedule_sendsAppropriateBroadcast() { - ArgumentCaptor pmQueryIntentCaptor = mockPackageManagerInfo(); - - Job job = mDispatcher.newJobBuilder() - .setConstraints(Constraint.DEVICE_CHARGING) - .setService(TestJobService.class) - .setTrigger(Trigger.executionWindow(0, 60)) - .setRecurring(false) - .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) - .setTag("foobar") - .build(); - - Intent pmQueryIntent = pmQueryIntentCaptor.getValue(); - assertEquals(JobService.ACTION_EXECUTE, pmQueryIntent.getAction()); - assertEquals(TestJobService.class.getName(), pmQueryIntent.getComponent().getClassName()); - - assertEquals("Expected schedule() request to succeed", - FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS, - mDispatcher.schedule(job)); - - final ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); - verify(mMockContext).sendBroadcast(captor.capture()); - - Intent broadcast = captor.getValue(); - - assertNotNull(broadcast); - assertEquals("com.google.android.gms.gcm.ACTION_SCHEDULE", broadcast.getAction()); - assertEquals("SCHEDULE_TASK", broadcast.getStringExtra("scheduler_action")); - assertEquals("com.google.android.gms", broadcast.getPackage()); - assertEquals(8, broadcast.getIntExtra("source", -1)); - assertEquals(1, broadcast.getIntExtra("source_version", -1)); - - final Parcelable parcelablePendingIntent = broadcast.getParcelableExtra("app"); - assertTrue("Expected 'app' value to be a PendingIntent", - parcelablePendingIntent instanceof PendingIntent); - } - - private ArgumentCaptor mockPackageManagerInfo() { - PackageManager packageManager = mock(PackageManager.class); - when(mMockContext.getPackageManager()).thenReturn(packageManager); - ArgumentCaptor intentArgCaptor = ArgumentCaptor.forClass(Intent.class); - - ResolveInfo info = new ResolveInfo(); - info.serviceInfo = new ServiceInfo(); - info.serviceInfo.enabled = true; - - //noinspection WrongConstant - when(packageManager.queryIntentServices(intentArgCaptor.capture(), eq(0))) - .thenReturn(Arrays.asList(info)); - - return intentArgCaptor; - } - - @Test - public void testCancel_sendsAppropriateBroadcast() { - mDispatcher.cancel("foobar"); - - ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); - verify(mMockContext).sendBroadcast(captor.capture()); - - Intent broadcast = captor.getValue(); - - assertNotNull(broadcast); - assertEquals("foobar", broadcast.getStringExtra("tag")); - } - - private void markBackendUnavailable() { - mDriver.available = false; - } - - public final static class TestJobDriver implements Driver { - public boolean available = true; - - private final Driver wrappedDriver; - - public TestJobDriver(Driver wrappedDriver) { - this.wrappedDriver = wrappedDriver; - } - - @Override - public int schedule(@NonNull Job job) { - return this.wrappedDriver.schedule(job); - } - - @Override - public int cancel(@NonNull String tag) { - return this.wrappedDriver.cancel(tag); - } - - @Override - public int cancelAll() { - return this.wrappedDriver.cancelAll(); - } - - @NonNull - @Override - public JobValidator getValidator() { - return this.wrappedDriver.getValidator(); - } - - @Override - public boolean isAvailable() { - return available; - } - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayJobWriterTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayJobWriterTest.java deleted file mode 100644 index c56ab0d08..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayJobWriterTest.java +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.net.Uri; -import android.os.Bundle; -import android.provider.ContactsContract; -import com.firebase.jobdispatcher.Job.Builder; -import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger; -import com.firebase.jobdispatcher.ObservedUri.Flags; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23) -public class GooglePlayJobWriterTest { - - private static final boolean[] ALL_BOOLEANS = {true, false}; - - private GooglePlayJobWriter mWriter; - - private static Builder initializeDefaultBuilder() { - return TestUtil.getBuilderWithNoopValidator() - .setConstraints(Constraint.DEVICE_CHARGING) - .setExtras(null) - .setLifetime(Lifetime.FOREVER) - .setRecurring(false) - .setReplaceCurrent(false) - .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) - .setService(TestJobService.class) - .setTag("tag") - .setTrigger(Trigger.NOW); - } - - @Before - public void setUp() throws Exception { - mWriter = new GooglePlayJobWriter(); - } - - @Test - public void testWriteToBundle_tags() { - for (String tag : Arrays.asList("foo", "bar", "foobar", "this is a tag")) { - Bundle b = mWriter.writeToBundle( - initializeDefaultBuilder().setTag(tag).build(), - new Bundle()); - - assertEquals("tag", tag, b.getString("tag")); - } - } - - @Test - public void testWriteToBundle_updateCurrent() { - for (boolean replaceCurrent : ALL_BOOLEANS) { - Bundle b = mWriter.writeToBundle( - initializeDefaultBuilder().setReplaceCurrent(replaceCurrent).build(), - new Bundle()); - - assertEquals("update_current", replaceCurrent, b.getBoolean("update_current")); - } - } - - @Test - public void testWriteToBundle_persisted() { - Bundle b = mWriter.writeToBundle( - initializeDefaultBuilder().setLifetime(Lifetime.FOREVER).build(), - new Bundle()); - - assertTrue("persisted", b.getBoolean("persisted")); - - for (int lifetime : new int[]{Lifetime.UNTIL_NEXT_BOOT}) { - b = mWriter.writeToBundle( - initializeDefaultBuilder().setLifetime(lifetime).build(), - new Bundle()); - - assertFalse("persisted", b.getBoolean("persisted")); - } - } - - @Test - public void testWriteToBundle_service() { - Bundle b = mWriter.writeToBundle( - initializeDefaultBuilder().setService(TestJobService.class).build(), - new Bundle()); - - assertEquals("service", GooglePlayReceiver.class.getName(), b.getString("service")); - } - - @Test - public void testWriteToBundle_requiredNetwork() { - Map mapping = new HashMap<>(); - mapping.put(Constraint.ON_ANY_NETWORK, GooglePlayJobWriter.LEGACY_NETWORK_CONNECTED); - mapping.put(Constraint.ON_UNMETERED_NETWORK, GooglePlayJobWriter.LEGACY_NETWORK_UNMETERED); - mapping.put(0, GooglePlayJobWriter.LEGACY_NETWORK_ANY); - - for (Entry testCase : mapping.entrySet()) { - @SuppressWarnings("WrongConstant") - Bundle b = mWriter.writeToBundle( - initializeDefaultBuilder().setConstraints(testCase.getKey()).build(), - new Bundle()); - - assertEquals("requiredNetwork", (int) testCase.getValue(), b.getInt("requiredNetwork")); - } - } - - @Test - public void testWriteToBundle_unmeteredConstraintShouldTakePrecendence() { - Bundle b = mWriter.writeToBundle( - initializeDefaultBuilder() - .setConstraints(Constraint.ON_ANY_NETWORK, Constraint.ON_UNMETERED_NETWORK) - .build(), - new Bundle()); - - assertEquals("expected ON_UNMETERED_NETWORK to take precendence over ON_ANY_NETWORK", - GooglePlayJobWriter.LEGACY_NETWORK_UNMETERED, b.getInt("requiredNetwork")); - } - - @Test - public void testWriteToBundle_requiresCharging() { - assertTrue("requiresCharging", mWriter.writeToBundle( - initializeDefaultBuilder().setConstraints(Constraint.DEVICE_CHARGING).build(), - new Bundle()).getBoolean("requiresCharging")); - - for (Integer constraint : Arrays.asList( - Constraint.ON_ANY_NETWORK, - Constraint.ON_UNMETERED_NETWORK)) { - - assertFalse("requiresCharging", mWriter.writeToBundle( - initializeDefaultBuilder().setConstraints(constraint).build(), - new Bundle()).getBoolean("requiresCharging")); - } - } - - @Test - public void testWriteToBundle_requiresIdle() { - assertTrue("requiresIdle", mWriter.writeToBundle( - initializeDefaultBuilder().setConstraints(Constraint.DEVICE_IDLE).build(), - new Bundle()).getBoolean("requiresIdle")); - - for (Integer constraint : Arrays.asList( - Constraint.ON_ANY_NETWORK, - Constraint.ON_UNMETERED_NETWORK)) { - - assertFalse("requiresIdle", mWriter.writeToBundle( - initializeDefaultBuilder().setConstraints(constraint).build(), - new Bundle()).getBoolean("requiresIdle")); - } - } - - @Test - public void testWriteToBundle_retryPolicy() { - assertEquals("retry_policy", - GooglePlayJobWriter.LEGACY_RETRY_POLICY_EXPONENTIAL, - mWriter.writeToBundle( - initializeDefaultBuilder() - .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) - .build(), - new Bundle()).getBundle("retryStrategy").getInt("retry_policy")); - - assertEquals("retry_policy", - GooglePlayJobWriter.LEGACY_RETRY_POLICY_LINEAR, - mWriter.writeToBundle( - initializeDefaultBuilder() - .setRetryStrategy(RetryStrategy.DEFAULT_LINEAR) - .build(), - new Bundle()).getBundle("retryStrategy").getInt("retry_policy")); - } - - @Test - public void testWriteToBundle_backoffSeconds() { - for (RetryStrategy retryStrategy : Arrays - .asList(RetryStrategy.DEFAULT_EXPONENTIAL, RetryStrategy.DEFAULT_LINEAR)) { - - Bundle b = mWriter.writeToBundle( - initializeDefaultBuilder().setRetryStrategy(retryStrategy).build(), - new Bundle()).getBundle("retryStrategy"); - - assertEquals("initial_backoff_seconds", - retryStrategy.getInitialBackoff(), - b.getInt("initial_backoff_seconds")); - - assertEquals("maximum_backoff_seconds", - retryStrategy.getMaximumBackoff(), - b.getInt("maximum_backoff_seconds")); - - } - } - - @Test - public void testWriteToBundle_triggers() { - // immediate - Bundle b = mWriter.writeToBundle( - initializeDefaultBuilder().setTrigger(Trigger.NOW).build(), - new Bundle()); - - assertEquals("window_start", 0, b.getLong("window_start")); - assertEquals("window_end", 30, b.getLong("window_end")); - - // execution window (oneoff) - JobTrigger.ExecutionWindowTrigger t = Trigger.executionWindow(631, 978); - b = mWriter.writeToBundle( - initializeDefaultBuilder().setTrigger(t).build(), - new Bundle()); - - assertEquals("window_start", t.getWindowStart(), b.getLong("window_start")); - assertEquals("window_end", t.getWindowEnd(), b.getLong("window_end")); - - // execution window (periodic) - b = mWriter.writeToBundle( - initializeDefaultBuilder().setRecurring(true).setTrigger(t).build(), - new Bundle()); - - assertEquals("period", t.getWindowEnd(), b.getLong("period")); - assertEquals("period_flex", t.getWindowEnd() - t.getWindowStart(), b.getLong("period_flex")); - } - - @Test - public void testWriteToBundle_contentUriTrigger() { - ObservedUri observedUri = new ObservedUri(ContactsContract.AUTHORITY_URI, - Flags.FLAG_NOTIFY_FOR_DESCENDANTS); - ContentUriTrigger contentUriTrigger = Trigger.contentUriTrigger(Arrays.asList(observedUri)); - Bundle bundle = mWriter.writeToBundle( - initializeDefaultBuilder().setTrigger(contentUriTrigger).build(), new Bundle()); - Uri[] uris = - (Uri[]) bundle.getParcelableArray(BundleProtocol.PACKED_PARAM_CONTENT_URI_ARRAY); - int[] flags = bundle.getIntArray(BundleProtocol.PACKED_PARAM_CONTENT_URI_FLAGS_ARRAY); - assertTrue("Array size", uris.length == flags.length && flags.length == 1); - assertEquals(BundleProtocol.PACKED_PARAM_CONTENT_URI_ARRAY, - ContactsContract.AUTHORITY_URI, uris[0]); - assertEquals(BundleProtocol.PACKED_PARAM_CONTENT_URI_FLAGS_ARRAY, - Flags.FLAG_NOTIFY_FOR_DESCENDANTS, flags[0]); - } - - @Test - public void testWriteToBundle_extras() { - Bundle extras = new Bundle(); - - Bundle result = mWriter.writeToBundle( - initializeDefaultBuilder().setExtras(extras).build(), - new Bundle()); - - assertEquals("extras", extras, result.getBundle("extras")); - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayMessageHandlerTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayMessageHandlerTest.java deleted file mode 100644 index 31969a16e..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayMessageHandlerTest.java +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static com.firebase.jobdispatcher.GooglePlayJobWriter.REQUEST_PARAM_TAG; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.AppOpsManager; -import android.content.Context; -import android.os.Bundle; -import android.os.Looper; -import android.os.Message; -import android.os.Messenger; -import com.firebase.jobdispatcher.JobInvocation.Builder; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -/** - * Tests {@link GooglePlayMessageHandler}. - */ -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 21) -public class GooglePlayMessageHandlerTest { - - @Mock - Looper looper; - @Mock - GooglePlayReceiver receiverMock; - @Mock - Context context; - @Mock - AppOpsManager appOpsManager; - @Mock - Messenger messengerMock; - @Mock - ExecutionDelegator executionDelegatorMock; - - GooglePlayMessageHandler handler; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - handler = new GooglePlayMessageHandler(looper, receiverMock); - when(receiverMock.getExecutionDelegator()).thenReturn(executionDelegatorMock); - when(receiverMock.getApplicationContext()).thenReturn(context); - when(context.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(appOpsManager); - } - - @Test - public void handleMessage_nullNoException() throws Exception { - handler.handleMessage(null); - } - - @Test - public void handleMessage_ignoreIfSenderIsNotGcm() throws Exception { - Message message = Message.obtain(); - message.what = GooglePlayMessageHandler.MSG_START_EXEC; - Bundle data = new Bundle(); - data.putString(REQUEST_PARAM_TAG, "TAG"); - message.setData(data); - message.replyTo = messengerMock; - doThrow(new SecurityException()).when(appOpsManager) - .checkPackage(message.sendingUid, GooglePlayDriver.BACKEND_PACKAGE); - handler.handleMessage(message); - verify(receiverMock, never()).prepareJob(any(GooglePlayMessengerCallback.class), eq(data)); - } - - @Test - public void handleMessage_startExecution_noData() throws Exception { - Message message = Message.obtain(); - message.what = GooglePlayMessageHandler.MSG_START_EXEC; - message.replyTo = messengerMock; - - handler.handleMessage(message); - verify(receiverMock, never()) - .prepareJob(any(GooglePlayMessengerCallback.class), any(Bundle.class)); - } - - @Test - public void handleMessage_startExecution() throws Exception { - Message message = Message.obtain(); - message.what = GooglePlayMessageHandler.MSG_START_EXEC; - Bundle data = new Bundle(); - data.putString(REQUEST_PARAM_TAG, "TAG"); - message.setData(data); - message.replyTo = messengerMock; - JobInvocation jobInvocation = new Builder() - .setTag("tag") - .setService(TestJobService.class.getName()) - .setTrigger(Trigger.NOW).build(); - when(receiverMock.prepareJob(any(GooglePlayMessengerCallback.class), eq(data))) - .thenReturn(jobInvocation); - - handler.handleMessage(message); - - verify(executionDelegatorMock).executeJob(jobInvocation); - } - - @Test - public void handleMessage_stopExecution() throws Exception { - Message message = Message.obtain(); - message.what = GooglePlayMessageHandler.MSG_STOP_EXEC; - JobCoder jobCoder = GooglePlayReceiver.getJobCoder(); - Bundle data = TestUtil.encodeContentUriJob(TestUtil.getContentUriTrigger(), jobCoder); - JobInvocation jobInvocation = jobCoder.decode(data).build(); - message.setData(data); - message.replyTo = messengerMock; - - handler.handleMessage(message); - - final ArgumentCaptor captor = ArgumentCaptor.forClass(JobInvocation.class); - - verify(executionDelegatorMock).stopJob(captor.capture()); - - TestUtil.assertJobsEqual(jobInvocation, captor.getValue()); - } - - @Test - public void handleMessage_stopExecution_invalidNoCrash() throws Exception { - Message message = Message.obtain(); - message.what = GooglePlayMessageHandler.MSG_STOP_EXEC; - message.replyTo = messengerMock; - - handler.handleMessage(message); - - verify(executionDelegatorMock, never()).stopJob(any(JobInvocation.class)); - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayMessengerCallbackTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayMessengerCallbackTest.java deleted file mode 100644 index b71cf003a..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayMessengerCallbackTest.java +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static com.firebase.jobdispatcher.GooglePlayJobWriter.REQUEST_PARAM_TAG; -import static org.junit.Assert.assertEquals; - -import android.os.Message; -import android.os.Messenger; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -/** - * Tests {@link GooglePlayMessengerCallback}. - */ -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 21) -public class GooglePlayMessengerCallbackTest { - - @Mock - Messenger messengerMock; - GooglePlayMessengerCallback callback; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - callback = new GooglePlayMessengerCallback(messengerMock, "tag"); - } - - @Test - public void jobFinished() throws Exception { - final ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); - - callback.jobFinished(JobService.RESULT_SUCCESS); - - Mockito.verify(messengerMock).send(messageCaptor.capture()); - Message message = messageCaptor.getValue(); - assertEquals(message.what, GooglePlayMessageHandler.MSG_RESULT); - assertEquals(message.arg1, JobService.RESULT_SUCCESS); - assertEquals(message.getData().getString(REQUEST_PARAM_TAG), "tag"); - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayReceiverTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayReceiverTest.java deleted file mode 100644 index 47d7da573..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/GooglePlayReceiverTest.java +++ /dev/null @@ -1,327 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static com.firebase.jobdispatcher.TestUtil.encodeContentUriJob; -import static com.firebase.jobdispatcher.TestUtil.encodeRecurringContentUriJob; -import static com.firebase.jobdispatcher.TestUtil.getContentUriTrigger; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNull; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -import android.app.Service; -import android.content.Intent; -import android.net.Uri; -import android.os.Binder; -import android.os.Build.VERSION_CODES; -import android.os.Bundle; -import android.os.IBinder; -import android.os.Messenger; -import android.os.Parcel; -import android.provider.ContactsContract; -import android.provider.ContactsContract.Contacts; -import android.provider.MediaStore.Images.Media; -import android.support.annotation.NonNull; -import com.firebase.jobdispatcher.GooglePlayReceiverTest.ShadowMessenger; -import com.firebase.jobdispatcher.JobInvocation.Builder; -import com.firebase.jobdispatcher.TestUtil.InspectableBinder; -import com.google.android.gms.gcm.PendingCallback; -import java.util.ArrayList; -import java.util.Arrays; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implements; - -@RunWith(RobolectricTestRunner.class) -@Config( - constants = BuildConfig.class, - manifest = Config.NONE, - sdk = 21, - shadows = {ShadowMessenger.class} -) -public class GooglePlayReceiverTest { - - /** - * The default ShadowMessenger implementation causes NPEs when using the - * {@link Messenger#Messenger(Handler)} constructor. We create our own empty Shadow so we can - * just use the standard Android implementation, which is totally fine. - * - * @see Robolectric issue - * - */ - @Implements(Messenger.class) - public static class ShadowMessenger {} - - GooglePlayReceiver receiver; - - JobCoder jobCoder = new JobCoder(BundleProtocol.PACKED_PARAM_BUNDLE_PREFIX, true); - - @Mock - Messenger messengerMock; - @Mock - IBinder binderMock; - @Mock - JobCallback callbackMock; - @Mock - ExecutionDelegator executionDelegatorMock; - @Mock - Driver driverMock; - @Captor - ArgumentCaptor jobArgumentCaptor; - - ArrayList triggeredUris = new ArrayList<>(); - - { - triggeredUris.add(ContactsContract.AUTHORITY_URI); - triggeredUris.add(Media.EXTERNAL_CONTENT_URI); - } - - Builder jobInvocationBuilder = new Builder() - .setTag("tag") - .setService(TestJobService.class.getName()) - .setTrigger(Trigger.NOW); - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - receiver = spy(new GooglePlayReceiver()); - when(receiver.getExecutionDelegator()).thenReturn(executionDelegatorMock); - receiver.driver = driverMock; - receiver.validationEnforcer = new ValidationEnforcer(new NoopJobValidator()); - } - - @Test - public void onJobFinished_unknownJobCallbackIsNotPresent_ignoreNoException() { - receiver.onJobFinished(jobInvocationBuilder.build(), JobService.RESULT_SUCCESS); - - verifyZeroInteractions(driverMock); - } - - @Test - public void onJobFinished_notRecurringContentJob_sendResult() { - jobInvocationBuilder.setTrigger( - Trigger.contentUriTrigger(Arrays.asList(new ObservedUri(Contacts.CONTENT_URI, 0)))); - - JobInvocation jobInvocation = receiver - .prepareJob(callbackMock, getBundleForContentJobExecution()); - - receiver.onJobFinished(jobInvocation, JobService.RESULT_SUCCESS); - verify(callbackMock).jobFinished(JobService.RESULT_SUCCESS); - verifyZeroInteractions(driverMock); - } - - @Test - public void onJobFinished_successRecurringContentJob_reschedule() { - JobInvocation jobInvocation = receiver - .prepareJob(callbackMock, getBundleForContentJobExecutionRecurring()); - - receiver.onJobFinished(jobInvocation, JobService.RESULT_SUCCESS); - - verify(driverMock).schedule(jobArgumentCaptor.capture()); - - // No need to callback when job finished. - // Reschedule request is treated as two events: completion of old job and scheduling of new - // job with the same parameters. - verifyZeroInteractions(callbackMock); - - Job rescheduledJob = jobArgumentCaptor.getValue(); - TestUtil.assertJobsEqual(jobInvocation, rescheduledJob); - } - - @Test - public void onJobFinished_failWithRetryRecurringContentJob_sendResult() { - JobInvocation jobInvocation = receiver - .prepareJob(callbackMock, getBundleForContentJobExecutionRecurring()); - - receiver.onJobFinished(jobInvocation, JobService.RESULT_FAIL_RETRY); - - // If a job finishes with RESULT_FAIL_RETRY we don't need to send a reschedule request. - // Rescheduling will erase previously triggered URIs. - verify(callbackMock).jobFinished(JobService.RESULT_FAIL_RETRY); - - verifyZeroInteractions(driverMock); - } - - @Test - public void prepareJob() { - Intent intent = new Intent(); - - Bundle encode = encodeContentUriJob(getContentUriTrigger(), jobCoder); - intent.putExtra(GooglePlayJobWriter.REQUEST_PARAM_EXTRAS, encode); - - Parcel container = Parcel.obtain(); - container.writeStrongBinder(new Binder()); - PendingCallback pcb = new PendingCallback(container); - intent.putExtra("callback", pcb); - - ArrayList uris = new ArrayList<>(); - uris.add(ContactsContract.AUTHORITY_URI); - uris.add(Media.EXTERNAL_CONTENT_URI); - intent.putParcelableArrayListExtra(BundleProtocol.PACKED_PARAM_TRIGGERED_URIS, uris); - - JobInvocation jobInvocation = receiver.prepareJob(intent); - assertEquals(jobInvocation.getTriggerReason().getTriggeredContentUris(), uris); - } - - @Test - public void prepareJob_messenger() { - JobInvocation jobInvocation = receiver.prepareJob(callbackMock, new Bundle()); - assertNull(jobInvocation); - verify(callbackMock).jobFinished(JobService.RESULT_FAIL_NORETRY); - } - - @Test - public void prepareJob_messenger_noExtras() { - Bundle bundle = getBundleForContentJobExecution(); - - JobInvocation jobInvocation = receiver.prepareJob(callbackMock, bundle); - assertEquals(jobInvocation.getTriggerReason().getTriggeredContentUris(), triggeredUris); - } - - @NonNull - private Bundle getBundleForContentJobExecution() { - Bundle bundle = new Bundle(); - - Bundle encode = encodeContentUriJob(getContentUriTrigger(), jobCoder); - bundle.putBundle(GooglePlayJobWriter.REQUEST_PARAM_EXTRAS, encode); - - bundle.putParcelableArrayList(BundleProtocol.PACKED_PARAM_TRIGGERED_URIS, triggeredUris); - return bundle; - } - - @NonNull - private Bundle getBundleForContentJobExecutionRecurring() { - Bundle bundle = new Bundle(); - - Bundle encode = encodeRecurringContentUriJob(getContentUriTrigger(), jobCoder); - bundle.putBundle(GooglePlayJobWriter.REQUEST_PARAM_EXTRAS, encode); - - bundle.putParcelableArrayList(BundleProtocol.PACKED_PARAM_TRIGGERED_URIS, triggeredUris); - return bundle; - } - - @Test - public void onBind() { - Intent intent = new Intent(GooglePlayReceiver.ACTION_EXECUTE); - IBinder binderA = receiver.onBind(intent); - IBinder binderB = receiver.onBind(intent); - - assertEquals(binderA, binderB); - } - - @Test - public void onBind_nullIntent() { - IBinder binder = receiver.onBind(null); - assertNull(binder); - } - - @Test - public void onBind_wrongAction() { - Intent intent = new Intent("test"); - IBinder binder = receiver.onBind(intent); - assertNull(binder); - } - - @Test - @Config(sdk = VERSION_CODES.KITKAT) - public void onBind_wrongBuild() { - Intent intent = new Intent(GooglePlayReceiver.ACTION_EXECUTE); - IBinder binder = receiver.onBind(intent); - assertNull(binder); - } - - @Test - public void onStartCommand_nullIntent() { - assertResultWasStartNotSticky(receiver.onStartCommand(null, 0, 101)); - verify(receiver).stopSelf(101); - } - - @Test - public void onStartCommand_initAction() { - Intent initIntent = new Intent("com.google.android.gms.gcm.SERVICE_ACTION_INITIALIZE"); - assertResultWasStartNotSticky(receiver.onStartCommand(initIntent, 0, 101)); - verify(receiver).stopSelf(101); - } - - @Test - public void onStartCommand_unknownAction() { - Intent unknownIntent = new Intent("com.example.foo.bar"); - assertResultWasStartNotSticky(receiver.onStartCommand(unknownIntent, 0, 101)); - assertResultWasStartNotSticky(receiver.onStartCommand(unknownIntent, 0, 102)); - assertResultWasStartNotSticky(receiver.onStartCommand(unknownIntent, 0, 103)); - - InOrder inOrder = inOrder(receiver); - inOrder.verify(receiver).stopSelf(101); - inOrder.verify(receiver).stopSelf(102); - inOrder.verify(receiver).stopSelf(103); - } - - @Test - public void onStartCommand_executeActionWithEmptyExtras() { - Intent execIntent = new Intent("com.google.android.gms.gcm.ACTION_TASK_READY"); - assertResultWasStartNotSticky(receiver.onStartCommand(execIntent, 0, 101)); - verify(receiver).stopSelf(101); - } - - @Test - public void onStartCommand_executeAction() { - JobInvocation job = new JobInvocation.Builder() - .setTag("tag") - .setService("com.example.foo.FooService") - .setTrigger(Trigger.NOW) - .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) - .setLifetime(Lifetime.UNTIL_NEXT_BOOT) - .setConstraints(new int[]{Constraint.DEVICE_IDLE}) - .build(); - - Intent execIntent = new Intent("com.google.android.gms.gcm.ACTION_TASK_READY") - .putExtra("extras", new JobCoder(BundleProtocol.PACKED_PARAM_BUNDLE_PREFIX, true) - .encode(job, new Bundle())) - .putExtra("callback", new InspectableBinder().toPendingCallback()); - - when(executionDelegatorMock.executeJob(any(JobInvocation.class))).thenReturn(true); - - assertResultWasStartNotSticky(receiver.onStartCommand(execIntent, 0, 101)); - - verify(receiver, never()).stopSelf(anyInt()); - verify(executionDelegatorMock).executeJob(any(JobInvocation.class)); - - receiver.onJobFinished(job, JobService.RESULT_SUCCESS); - - verify(receiver).stopSelf(101); - } - - private void assertResultWasStartNotSticky(int result) { - assertEquals( - "Result for onStartCommand wasn't START_NOT_STICKY", Service.START_NOT_STICKY, result); - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ImmediateTriggerTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ImmediateTriggerTest.java deleted file mode 100644 index 1cf57ee68..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ImmediateTriggerTest.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23) -public class ImmediateTriggerTest { - /** - * Code coverage. - */ - @Test - public void testPrivateConstructor() throws Exception { - TestUtil.assertHasSinglePrivateConstructor(JobTrigger.ImmediateTrigger.class); - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobBuilderTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobBuilderTest.java deleted file mode 100644 index ffb4026e8..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobBuilderTest.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static org.junit.Assert.assertEquals; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23) -public class JobBuilderTest { - private static final int[] ALL_LIFETIMES = {Lifetime.UNTIL_NEXT_BOOT, Lifetime.FOREVER}; - - private Job.Builder mBuilder; - - @Before - public void setUp() throws Exception { - mBuilder = TestUtil.getBuilderWithNoopValidator(); - } - - @Test - public void testAddConstraints() { - mBuilder.setConstraints() - .addConstraint(Constraint.DEVICE_CHARGING) - .addConstraint(Constraint.ON_UNMETERED_NETWORK); - - int[] expected = {Constraint.DEVICE_CHARGING, Constraint.ON_UNMETERED_NETWORK}; - - assertEquals(Constraint.compact(expected), Constraint.compact(mBuilder.getConstraints())); - } - - @Test - public void testSetLifetime() { - for (int lifetime : ALL_LIFETIMES) { - mBuilder.setLifetime(lifetime); - assertEquals(lifetime, mBuilder.getLifetime()); - } - } - - @Test - public void testSetShouldReplaceCurrent() { - for (boolean replace : new boolean[]{true, false}) { - mBuilder.setReplaceCurrent(replace); - assertEquals(replace, mBuilder.shouldReplaceCurrent()); - } - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobCoderTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobCoderTest.java deleted file mode 100644 index bd3ebfb94..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobCoderTest.java +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static com.firebase.jobdispatcher.TestUtil.encodeContentUriJob; -import static com.firebase.jobdispatcher.TestUtil.getContentUriTrigger; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -import android.net.Uri; -import android.os.Bundle; -import android.provider.ContactsContract; -import android.provider.MediaStore.Images.Media; -import com.firebase.jobdispatcher.Job.Builder; -import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger; -import java.util.ArrayList; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23) -public class JobCoderTest { - private final JobCoder mCoder = new JobCoder(PREFIX, true); - private static final String PREFIX = "prefix"; - private Builder mBuilder; - - private static Builder setValidBuilderDefaults(Builder mBuilder) { - return mBuilder - .setTag("tag") - .setTrigger(Trigger.NOW) - .setService(TestJobService.class) - .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL); - } - - @Before - public void setUp() throws Exception { - mBuilder = TestUtil.getBuilderWithNoopValidator(); - } - - @Test - public void testCodingIsLossless() { - for (JobParameters input : TestUtil.getJobCombinations(mBuilder)) { - - JobParameters output = mCoder.decode(mCoder.encode(input, input.getExtras())).build(); - - TestUtil.assertJobsEqual(input, output); - } - } - - @Test(expected = IllegalArgumentException.class) - public void testEncode_throwsOnNullBundle() { - mCoder.encode(mBuilder.build(), null); - } - - @Test(expected = IllegalArgumentException.class) - public void testDecode_throwsOnNullBundle() { - mCoder.decode(null); - } - - @Test - public void testDecode_failsWhenMissingFields() { - assertNull("Expected null tag to cause decoding to fail", - mCoder.decode(mCoder.encode( - setValidBuilderDefaults(mBuilder).setTag(null).build(), - new Bundle()))); - - assertNull("Expected null service to cause decoding to fail", - mCoder.decode(mCoder.encode( - setValidBuilderDefaults(mBuilder).setService(null).build(), - new Bundle()))); - } - - @Test(expected = IllegalArgumentException.class) - public void testDecode_failsUnsupportedTrigger() { - mCoder.decode(mCoder.encode(setValidBuilderDefaults(mBuilder).setTrigger(null).build(), - new Bundle())); - } - - @Test - public void testDecode_ignoresMissingRetryStrategy() { - assertNotNull("Expected null retry strategy to cause decode to use a default", - mCoder.decode(mCoder.encode( - setValidBuilderDefaults(mBuilder).setRetryStrategy(null).build(), - new Bundle()))); - } - - @Test - public void encode_contentUriTrigger() { - Bundle encode = TestUtil.encodeContentUriJob(TestUtil.getContentUriTrigger(), mCoder); - int triggerType = encode.getInt(PREFIX + BundleProtocol.PACKED_PARAM_TRIGGER_TYPE); - assertEquals("Trigger type", BundleProtocol.TRIGGER_TYPE_CONTENT_URI, triggerType); - - String json = encode.getString(PREFIX + BundleProtocol.PACKED_PARAM_OBSERVED_URI); - String expectedJson = "{\"uri_flags\":[1,0],\"uris\":[\"content:\\/\\/com.android.contacts" - + "\",\"content:\\/\\/media\\/external\\/images\\/media\"]}"; - assertEquals("Json trigger", expectedJson, json); - } - - @Test - public void decode_contentUriTrigger() { - ContentUriTrigger contentUriTrigger = TestUtil.getContentUriTrigger(); - Bundle bundle = TestUtil.encodeContentUriJob(contentUriTrigger, mCoder); - JobInvocation decode = mCoder.decode(bundle).build(); - ContentUriTrigger trigger = (ContentUriTrigger) decode.getTrigger(); - assertEquals(contentUriTrigger.getUris(), trigger.getUris()); - } - - @Test - public void decode_addBundleAsExtras() { - ContentUriTrigger contentUriTrigger = TestUtil.getContentUriTrigger(); - Bundle bundle = TestUtil.encodeContentUriJob(contentUriTrigger, mCoder); - bundle.putString("test_key", "test_value"); - JobInvocation decode = mCoder.decode(bundle).build(); - assertEquals("test_value", decode.getExtras().getString("test_key")); - } - - @Test - public void decodeIntentBundle() { - Bundle bundle = new Bundle(); - - ContentUriTrigger uriTrigger = getContentUriTrigger(); - Bundle encode = encodeContentUriJob(uriTrigger, mCoder); - bundle.putBundle(GooglePlayJobWriter.REQUEST_PARAM_EXTRAS, encode); - - ArrayList uris = new ArrayList<>(); - uris.add(ContactsContract.AUTHORITY_URI); - uris.add(Media.EXTERNAL_CONTENT_URI); - bundle.putParcelableArrayList(BundleProtocol.PACKED_PARAM_TRIGGERED_URIS, uris); - - JobInvocation jobInvocation = mCoder.decodeIntentBundle(bundle); - - assertEquals(uris, jobInvocation.getTriggerReason().getTriggeredContentUris()); - assertEquals("TAG", jobInvocation.getTag()); - assertEquals(uriTrigger.getUris(), ((ContentUriTrigger) jobInvocation.getTrigger()) - .getUris()); - assertEquals(TestJobService.class.getName(), jobInvocation.getService()); - assertEquals(RetryStrategy.DEFAULT_EXPONENTIAL.getPolicy(), - jobInvocation.getRetryStrategy().getPolicy()); - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobInvocationTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobInvocationTest.java deleted file mode 100644 index 769dd19d6..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobInvocationTest.java +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import android.os.Bundle; -import com.firebase.jobdispatcher.JobInvocation.Builder; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23) -public class JobInvocationTest { - private Builder builder; - - @Before - public void setUp() { - builder = new Builder() - .setTag("tag") - .setService(TestJobService.class.getName()) - .setTrigger(Trigger.NOW); - } - - @SuppressWarnings("ConstantConditions") - @Test - public void testShouldReplaceCurrent() throws Exception { - assertTrue("Expected shouldReplaceCurrent() to return value passed in constructor", - builder.setReplaceCurrent(true).build().shouldReplaceCurrent()); - assertFalse("Expected shouldReplaceCurrent() to return value passed in constructor", - builder.setReplaceCurrent(false).build().shouldReplaceCurrent()); - } - - @Test - public void extras() throws Exception { - assertNotNull(builder.build().getExtras()); - - Bundle bundle = new Bundle(); - bundle.putLong("test", 1L); - Bundle extras = builder.addExtras(bundle).build().getExtras(); - assertEquals(1, extras.size()); - assertEquals(1L, extras.getLong("test")); - } - - @Test - public void contract_hashCode_equals() { - JobInvocation jobInvocation = builder.build(); - assertEquals(jobInvocation, builder.build()); - assertEquals(jobInvocation.hashCode(), builder.build().hashCode()); - JobInvocation jobInvocationNew = builder.setTag("new").build(); - assertNotEquals(jobInvocation, jobInvocationNew); - assertNotEquals(jobInvocation.hashCode(), jobInvocationNew.hashCode()); - } - - @Test - public void contract_hashCode_equals_triggerShouldBeIgnored() { - JobInvocation jobInvocation = builder.build(); - JobInvocation periodic = builder.setTrigger(Trigger.executionWindow(0, 1)).build(); - assertEquals(jobInvocation, periodic); - assertEquals(jobInvocation.hashCode(), periodic.hashCode()); - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobServiceConnectionTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobServiceConnectionTest.java deleted file mode 100644 index 8a8be89f3..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobServiceConnectionTest.java +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.os.IBinder; -import android.os.Message; -import com.firebase.jobdispatcher.JobInvocation.Builder; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -/** - * Test for {@link JobServiceConnection}. - */ -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23) -public class JobServiceConnectionTest { - - JobInvocation job = new Builder() - .setTag("tag") - .setService(TestJobService.class.getName()) - .setTrigger(Trigger.NOW) - .build(); - - @Mock - Message messageMock; - @Mock - JobService.LocalBinder binderMock; - @Mock - JobService jobServiceMock; - - JobServiceConnection connection; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - when(binderMock.getService()).thenReturn(jobServiceMock); - connection = new JobServiceConnection(job, messageMock); - } - - @Test - public void fullConnectionCycle() { - assertFalse(connection.isBound()); - - connection.onServiceConnected(null, binderMock); - verify(jobServiceMock).start(job, messageMock); - assertTrue(connection.isBound()); - - connection.onStop(); - verify(jobServiceMock).stop(job); - assertTrue(connection.isBound()); - - connection.onServiceDisconnected(null); - assertFalse(connection.isBound()); - } - - @Test - public void onServiceConnected_shouldNotSendExecutionRequestTwice() { - assertFalse(connection.isBound()); - - connection.onServiceConnected(null, binderMock); - verify(jobServiceMock).start(job, messageMock); - assertTrue(connection.isBound()); - reset(jobServiceMock); - - connection.onServiceConnected(null, binderMock); - verify(jobServiceMock, never()).start(job, messageMock); // start should not be called again - - connection.onStop(); - verify(jobServiceMock).stop(job); - assertTrue(connection.isBound()); - - connection.onServiceDisconnected(null); - assertFalse(connection.isBound()); - } - - @Test - public void stopOnUnboundConnection() { - assertFalse(connection.isBound()); - connection.onStop(); - verify(jobServiceMock, never()).onStopJob(job); - } - - @Test - public void onServiceConnectedWrongBinder() { - IBinder binder = mock(IBinder.class); - connection.onServiceConnected(null, binder); - assertFalse(connection.isBound()); - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobServiceTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobServiceTest.java deleted file mode 100644 index 83c7134f4..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/JobServiceTest.java +++ /dev/null @@ -1,346 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -import android.content.Intent; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.Message; -import android.os.Parcel; -import com.firebase.jobdispatcher.JobInvocation.Builder; -import com.google.android.gms.gcm.PendingCallback; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23) -public class JobServiceTest { - private static CountDownLatch countDownLatch; - - @Before - public void setUp() throws Exception {} - - @After - public void tearDown() throws Exception { - countDownLatch = null; - } - - @Test - public void testOnStartCommand_handlesNullIntent() throws Exception { - JobService service = spy(new ExampleJobService()); - int startId = 7; - - try { - service.onStartCommand(null, 0, startId); - - verify(service).stopSelf(startId); - } catch (NullPointerException npe) { - fail("Unexpected NullPointerException after calling onStartCommand with a null Intent."); - } - } - - @Test - public void testOnStartCommand_handlesNullAction() throws Exception { - JobService service = spy(new ExampleJobService()); - int startId = 7; - - Intent nullActionIntent = new Intent(); - service.onStartCommand(nullActionIntent, 0, startId); - - verify(service).stopSelf(startId); - } - - @Test - public void testOnStartCommand_handlesEmptyAction() throws Exception { - JobService service = spy(new ExampleJobService()); - int startId = 7; - - Intent emptyActionIntent = new Intent(""); - service.onStartCommand(emptyActionIntent, 0, startId); - - verify(service).stopSelf(startId); - } - - @Test - public void testOnStartCommand_handlesUnknownAction() throws Exception { - JobService service = spy(new ExampleJobService()); - int startId = 7; - - Intent emptyActionIntent = new Intent("foo.bar.baz"); - service.onStartCommand(emptyActionIntent, 0, startId); - - verify(service).stopSelf(startId); - } - - @Test - public void testOnStartCommand_handlesStartJob_nullData() { - JobService service = spy(new ExampleJobService()); - int startId = 7; - - Intent executeJobIntent = new Intent(JobService.ACTION_EXECUTE); - service.onStartCommand(executeJobIntent, 0, startId); - - verify(service).stopSelf(startId); - } - - @Test - public void testOnStartCommand_handlesStartJob_noTag() { - JobService service = spy(new ExampleJobService()); - int startId = 7; - - Intent executeJobIntent = new Intent(JobService.ACTION_EXECUTE); - Parcel p = Parcel.obtain(); - p.writeStrongBinder(mock(IBinder.class)); - executeJobIntent.putExtra("callback", new PendingCallback(p)); - - service.onStartCommand(executeJobIntent, 0, startId); - - verify(service).stopSelf(startId); - - p.recycle(); - } - - @Test - public void testOnStartCommand_handlesStartJob_noCallback() { - JobService service = spy(new ExampleJobService()); - int startId = 7; - - Intent executeJobIntent = new Intent(JobService.ACTION_EXECUTE); - executeJobIntent.putExtra("tag", "foobar"); - - service.onStartCommand(executeJobIntent, 0, startId); - - verify(service).stopSelf(startId); - } - - @Test - public void testOnStartCommand_handlesStartJob_validRequest() throws InterruptedException { - JobService service = spy(new ExampleJobService()); - - HandlerThread ht = new HandlerThread("handler"); - ht.start(); - Handler h = new Handler(ht.getLooper()); - - Intent executeJobIntent = new Intent(JobService.ACTION_EXECUTE); - - Job jobSpec = TestUtil.getBuilderWithNoopValidator() - .setTag("tag") - .setService(ExampleJobService.class) - .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) - .setTrigger(Trigger.NOW) - .setLifetime(Lifetime.FOREVER) - .build(); - - countDownLatch = new CountDownLatch(1); - - ((JobService.LocalBinder) service.onBind(executeJobIntent)) - .getService() - .start(jobSpec, h.obtainMessage(ExecutionDelegator.JOB_FINISHED, jobSpec)); - - assertTrue("Expected job to run to completion", countDownLatch.await(5, TimeUnit.SECONDS)); - } - - @Test - public void testOnStartCommand_handlesStartJob_doNotStartRunningJobAgain() { - StoppableJobService service = new StoppableJobService(false); - - Job jobSpec = TestUtil.getBuilderWithNoopValidator() - .setTag("tag") - .setService(StoppableJobService.class) - .setTrigger(Trigger.NOW) - .build(); - - ((JobService.LocalBinder) service.onBind(null)).getService().start(jobSpec, null); - ((JobService.LocalBinder) service.onBind(null)).getService().start(jobSpec, null); - - assertEquals(1, service.getNumberOfExecutionRequestsReceived()); - } - - @Test - public void stop_noCallback_finished() { - JobService service = spy(new StoppableJobService(false)); - JobInvocation job = new Builder() - .setTag("Tag") - .setTrigger(Trigger.NOW) - .setService(StoppableJobService.class.getName()) - .build(); - service.stop(job); - verify(service, never()).onStopJob(job); - } - - @Test - public void stop_withCallback_retry() { - JobService service = spy(new StoppableJobService(false)); - - JobInvocation job = new Builder() - .setTag("Tag") - .setTrigger(Trigger.NOW) - .setService(StoppableJobService.class.getName()) - .build(); - - Handler handlerMock = mock(Handler.class); - Message message = Message.obtain(handlerMock); - service.start(job, message); - - service.stop(job); - verify(service).onStopJob(job); - verify(handlerMock).sendMessage(message); - assertEquals(message.arg1, JobService.RESULT_SUCCESS); - } - - @Test - public void stop_withCallback_done() { - JobService service = spy(new StoppableJobService(true)); - - JobInvocation job = new Builder() - .setTag("Tag") - .setTrigger(Trigger.NOW) - .setService(StoppableJobService.class.getName()) - .build(); - - Handler handlerMock = mock(Handler.class); - Message message = Message.obtain(handlerMock); - service.start(job, message); - - service.stop(job); - verify(service).onStopJob(job); - verify(handlerMock).sendMessage(message); - assertEquals(message.arg1, JobService.RESULT_FAIL_RETRY); - } - - @Test - public void onStartJob_jobFinishedReschedule() { - // Verify that a retry request from within onStartJob will cause the retry result to be sent - // to the bouncer service's handler, regardless of what value is ultimately returned from - // onStartJob. - JobService reschedulingService = new JobService() { - @Override - public boolean onStartJob(JobParameters job) { - // Reschedules job. - jobFinished(job, true /* retry this job */); - return false; - } - - @Override - public boolean onStopJob(JobParameters job) { - return false; - } - }; - - Job jobSpec = TestUtil.getBuilderWithNoopValidator() - .setTag("tag") - .setService(reschedulingService.getClass()) - .setTrigger(Trigger.NOW) - .build(); - Handler mock = mock(Handler.class); - Message message = new Message(); - message.setTarget(mock); - reschedulingService.start(jobSpec, message); - - verify(mock).sendMessage(message); - assertEquals(message.arg1, JobService.RESULT_FAIL_RETRY); - } - - @Test - public void onStartJob_jobFinishedNotReschedule() { - // Verify that a termination request from within onStartJob will cause the result to be sent - // to the bouncer service's handler, regardless of what value is ultimately returned from - // onStartJob. - JobService reschedulingService = new JobService() { - @Override - public boolean onStartJob(JobParameters job) { - jobFinished(job, false /* don't retry this job */); - return false; - } - - @Override - public boolean onStopJob(JobParameters job) { - return false; - } - }; - - Job jobSpec = TestUtil.getBuilderWithNoopValidator() - .setTag("tag") - .setService(reschedulingService.getClass()) - .setTrigger(Trigger.NOW) - .build(); - Handler mock = mock(Handler.class); - Message message = new Message(); - message.setTarget(mock); - reschedulingService.start(jobSpec, message); - - verify(mock).sendMessage(message); - assertEquals(message.arg1, JobService.RESULT_SUCCESS); - } - - public static class ExampleJobService extends JobService { - @Override - public boolean onStartJob(JobParameters job) { - countDownLatch.countDown(); - return false; - } - - @Override - public boolean onStopJob(JobParameters job) { - return false; - } - } - - public static class StoppableJobService extends JobService { - - private final boolean shouldReschedule; - - public int getNumberOfExecutionRequestsReceived() { - return amountOfExecutionRequestReceived.get(); - } - - private final AtomicInteger amountOfExecutionRequestReceived = new AtomicInteger(); - - public StoppableJobService(boolean shouldReschedule) { - this.shouldReschedule = shouldReschedule; - } - - @Override - public boolean onStartJob(JobParameters job) { - amountOfExecutionRequestReceived.incrementAndGet(); - return true; - } - - @Override - public boolean onStopJob(JobParameters job) { - return shouldReschedule; - } - - - } -} diff --git a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ValidationEnforcerTest.java b/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ValidationEnforcerTest.java deleted file mode 100644 index 2e4e939ac..000000000 --- a/jobdispatcher/src/test/java/com/firebase/jobdispatcher/ValidationEnforcerTest.java +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Collections; -import java.util.List; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 23) -public class ValidationEnforcerTest { - private static final List ERROR_LIST = Collections.singletonList("error: foo"); - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Mock - private JobValidator mValidator; - - @Mock - private JobParameters mMockJobParameters; - - @Mock - private JobTrigger mMockTrigger; - - private ValidationEnforcer mEnforcer; - private RetryStrategy mRetryStrategy = RetryStrategy.DEFAULT_EXPONENTIAL; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mEnforcer = new ValidationEnforcer(mValidator); - } - - @Test - public void testValidate_retryStrategy() throws Exception { - mEnforcer.validate(mRetryStrategy); - verify(mValidator).validate(mRetryStrategy); - } - - @Test - public void testValidate_jobSpec() throws Exception { - mEnforcer.validate(mMockJobParameters); - verify(mValidator).validate(mMockJobParameters); - } - - @Test - public void testValidate_trigger() throws Exception { - mEnforcer.validate(mMockTrigger); - verify(mValidator).validate(mMockTrigger); - } - - @Test - public void testIsValid_retryStrategy_invalid() throws Exception { - when(mValidator.validate(mRetryStrategy)) - .thenReturn(Collections.singletonList("error: foo")); - - assertFalse("isValid", mEnforcer.isValid(mRetryStrategy)); - } - - @Test - public void testIsValid_retryStrategy_valid() throws Exception { - when(mValidator.validate(mRetryStrategy)).thenReturn(null); - - assertTrue("isValid", mEnforcer.isValid(mRetryStrategy)); - - } - - @Test - public void testIsValid_trigger_invalid() throws Exception { - when(mValidator.validate(mMockTrigger)) - .thenReturn(Collections.singletonList("error: foo")); - - assertFalse("isValid", mEnforcer.isValid(mMockTrigger)); - } - - @Test - public void testIsValid_trigger_valid() throws Exception { - when(mValidator.validate(mMockTrigger)).thenReturn(null); - - assertTrue("isValid", mEnforcer.isValid(mMockTrigger)); - } - - @Test - public void testIsValid_jobSpec_invalid() throws Exception { - when(mValidator.validate(mMockJobParameters)).thenReturn(ERROR_LIST); - - assertFalse("isValid", mEnforcer.isValid(mMockJobParameters)); - } - - @Test - public void testIsValid_jobSpec_valid() throws Exception { - when(mValidator.validate(mMockJobParameters)).thenReturn(null); - - assertTrue("isValid", mEnforcer.isValid(mMockJobParameters)); - } - - @Test - public void testEnsureValid_retryStrategy_valid() throws Exception { - when(mValidator.validate(mRetryStrategy)).thenReturn(null); - - mEnforcer.ensureValid(mRetryStrategy); - } - - @Test - public void testEnsureValid_trigger_valid() throws Exception { - when(mValidator.validate(mMockTrigger)).thenReturn(null); - - mEnforcer.ensureValid(mMockTrigger); - } - - @Test - public void testEnsureValid_jobSpec_valid() throws Exception { - when(mValidator.validate(mMockJobParameters)).thenReturn(null); - - mEnforcer.ensureValid(mMockJobParameters); - } - - @Test - public void testEnsureValid_retryStrategy_invalid() throws Exception { - expectedException.expect(ValidationEnforcer.ValidationException.class); - - when(mValidator.validate(mRetryStrategy)).thenReturn(ERROR_LIST); - mEnforcer.ensureValid(mRetryStrategy); - } - - @Test - public void testEnsureValid_trigger_invalid() throws Exception { - expectedException.expect(ValidationEnforcer.ValidationException.class); - - when(mValidator.validate(mMockTrigger)).thenReturn(ERROR_LIST); - mEnforcer.ensureValid(mMockTrigger); - } - - @Test - public void testEnsureValid_jobSpec_invalid() throws Exception { - expectedException.expect(ValidationEnforcer.ValidationException.class); - - when(mValidator.validate(mMockJobParameters)).thenReturn(ERROR_LIST); - mEnforcer.ensureValid(mMockJobParameters); - } - - @Test - public void testValidationMessages() throws Exception { - when(mValidator.validate(mMockJobParameters)).thenReturn(ERROR_LIST); - - try { - mEnforcer.ensureValid(mMockJobParameters); - - fail("Expected ensureValid to fail"); - } catch (ValidationEnforcer.ValidationException ve) { - assertEquals("Expected ValidationException to have 1 error message", - 1, - ve.getErrors().size()); - } - } -} diff --git a/jobdispatcher/src/testLib/java/com/firebase/jobdispatcher/NoopJobValidator.java b/jobdispatcher/src/testLib/java/com/firebase/jobdispatcher/NoopJobValidator.java deleted file mode 100644 index 079a6eed2..000000000 --- a/jobdispatcher/src/testLib/java/com/firebase/jobdispatcher/NoopJobValidator.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import android.support.annotation.Nullable; -import java.util.List; - -/** - * A very simple Validator that thinks that everything is ok. Used for testing. - */ -class NoopJobValidator implements JobValidator { - - @Nullable - @Override - public List validate(JobParameters job) { - return null; - } - - @Nullable - @Override - public List validate(JobTrigger trigger) { - return null; - } - - @Nullable - @Override - public List validate(RetryStrategy retryStrategy) { - return null; - } -} diff --git a/jobdispatcher/src/testLib/java/com/firebase/jobdispatcher/TestJobService.java b/jobdispatcher/src/testLib/java/com/firebase/jobdispatcher/TestJobService.java deleted file mode 100644 index 77b3604ab..000000000 --- a/jobdispatcher/src/testLib/java/com/firebase/jobdispatcher/TestJobService.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -/** A very simple JobService that can be configured for individual tests. */ -public class TestJobService extends JobService { - - public interface JobServiceProxy { - boolean onStartJob(JobParameters job); - - boolean onStopJob(JobParameters job); - } - - public static final JobServiceProxy NOOP_PROXY = - new JobServiceProxy() { - @Override - public boolean onStartJob(JobParameters job) { - return false; - } - - @Override - public boolean onStopJob(JobParameters job) { - return false; - } - }; - - private static final Object lock = new Object(); - - // GuardedBy("lock") - private static JobServiceProxy currentProxy = NOOP_PROXY; - - public static void setProxy(JobServiceProxy proxy) { - synchronized (lock) { - currentProxy = proxy; - } - } - - public static void reset() { - synchronized (lock) { - currentProxy = NOOP_PROXY; - } - } - - @Override - public boolean onStartJob(JobParameters job) { - synchronized (lock) { - return currentProxy.onStartJob(job); - } - } - - @Override - public boolean onStopJob(JobParameters job) { - synchronized (lock) { - return currentProxy.onStopJob(job); - } - } -} diff --git a/jobdispatcher/src/testLib/java/com/firebase/jobdispatcher/TestUtil.java b/jobdispatcher/src/testLib/java/com/firebase/jobdispatcher/TestUtil.java deleted file mode 100644 index 85f9fadcd..000000000 --- a/jobdispatcher/src/testLib/java/com/firebase/jobdispatcher/TestUtil.java +++ /dev/null @@ -1,375 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.firebase.jobdispatcher; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import android.os.Binder; -import android.os.Bundle; -import android.os.Parcel; -import android.provider.ContactsContract; -import android.provider.MediaStore.Images.Media; -import android.support.annotation.NonNull; -import com.firebase.jobdispatcher.Job.Builder; -import com.firebase.jobdispatcher.JobTrigger.ContentUriTrigger; -import com.firebase.jobdispatcher.ObservedUri.Flags; -import com.google.android.gms.gcm.PendingCallback; -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -/** - * Provides common utilities helpful for testing. - */ -public class TestUtil { - - private static final String TAG = "TAG"; - private static final String[] TAG_COMBINATIONS = {"tag", "foobar", "fooooooo", "bz", "100"}; - - private static final int[] LIFETIME_COMBINATIONS = { - Lifetime.UNTIL_NEXT_BOOT, - Lifetime.FOREVER}; - - private static final JobTrigger[] TRIGGER_COMBINATIONS = { - Trigger.executionWindow(0, 30), - Trigger.executionWindow(300, 600), - Trigger.executionWindow(86400, 86400 * 2), - Trigger.NOW, - Trigger.contentUriTrigger( - Arrays.asList(new ObservedUri(ContactsContract.AUTHORITY_URI, 0))), - Trigger.contentUriTrigger(Arrays.asList(new ObservedUri(ContactsContract.AUTHORITY_URI, 0), - new ObservedUri(ContactsContract.AUTHORITY_URI, Flags.FLAG_NOTIFY_FOR_DESCENDANTS))) - }; - - @SuppressWarnings("unchecked") - private static final List> SERVICE_COMBINATIONS = - Arrays.asList(TestJobService.class); - - private static final RetryStrategy[] RETRY_STRATEGY_COMBINATIONS = { - RetryStrategy.DEFAULT_LINEAR, - new RetryStrategy(RetryStrategy.RETRY_POLICY_LINEAR, 60, 300), - RetryStrategy.DEFAULT_EXPONENTIAL, - new RetryStrategy(RetryStrategy.RETRY_POLICY_EXPONENTIAL, 300, 600), - }; - - public static void assertHasSinglePrivateConstructor(Class cls) throws Exception { - Constructor[] constructors = cls.getDeclaredConstructors(); - assertEquals("expected number of constructors to be == 1", 1, constructors.length); - - Constructor constructor = constructors[0]; - assertFalse("expected constructor to be inaccessible", constructor.isAccessible()); - - constructor.setAccessible(true); - constructor.newInstance(); - } - - static List> getAllConstraintCombinations() { - List> combos = new LinkedList<>(); - - combos.add(Collections.emptyList()); - for (Integer cur : Constraint.ALL_CONSTRAINTS) { - for (int l = combos.size() - 1; l >= 0; l--) { - List oldCombo = combos.get(l); - List newCombo = Arrays.asList(new Integer[oldCombo.size() + 1]); - - Collections.copy(newCombo, oldCombo); - newCombo.set(oldCombo.size(), cur); - combos.add(newCombo); - } - combos.add(Collections.singletonList(cur)); - } - - return combos; - } - - static int[] toIntArray(List list) { - int[] input = new int[list.size()]; - for (int i = 0; i < list.size(); i++) { - input[i] = list.get(i); - } - return input; - } - - static List getJobCombinations(Builder builder) { - return getCombination(new JobBuilder(builder)); - } - - static List getJobInvocationCombinations() { - return getCombination(new JobInvocationBuilder()); - } - - private static List getCombination( - JobParameterBuilder buildJobParam) { - - List result = new ArrayList<>(); - for (String tag : TAG_COMBINATIONS) { - for (List constraintList : getAllConstraintCombinations()) { - for (boolean recurring : new boolean[]{true, false}) { - for (boolean replaceCurrent : new boolean[]{true, false}) { - for (int lifetime : LIFETIME_COMBINATIONS) { - for (JobTrigger trigger : TRIGGER_COMBINATIONS) { - for (Class service : SERVICE_COMBINATIONS) { - for (Bundle extras : getBundleCombinations()) { - for (RetryStrategy rs : RETRY_STRATEGY_COMBINATIONS) { - result.add(buildJobParam.build( - tag, - replaceCurrent, - constraintList, - recurring, - lifetime, - trigger, - service, - extras, - rs)); - } - } - } - } - } - } - } - } - } - return result; - } - - private static Bundle[] getBundleCombinations() { - List bundles = new LinkedList<>(); - bundles.add(new Bundle()); - - Bundle b = new Bundle(); - b.putString("foo", "bar"); - b.putInt("bar", 1); - b.putLong("baz", 3L); - bundles.add(b); - - return bundles.toArray(new Bundle[bundles.size()]); - } - - static void assertJobsEqual(JobParameters input, JobParameters output) { - assertNotNull("input", input); - assertNotNull("output", output); - - assertEquals("isRecurring()", input.isRecurring(), output.isRecurring()); - assertEquals("shouldReplaceCurrent()", - input.shouldReplaceCurrent(), - output.shouldReplaceCurrent()); - assertEquals("getLifetime()", input.getLifetime(), output.getLifetime()); - assertEquals("getTag()", input.getTag(), output.getTag()); - assertEquals("getService()", input.getService(), output.getService()); - assertEquals("getConstraints()", - Constraint.compact(input.getConstraints()), - Constraint.compact(output.getConstraints())); - - assertTriggersEqual(input.getTrigger(), output.getTrigger()); - assertBundlesEqual(input.getExtras(), output.getExtras()); - assertRetryStrategiesEqual(input.getRetryStrategy(), output.getRetryStrategy()); - } - - static void assertRetryStrategiesEqual(RetryStrategy in, RetryStrategy out) { - String prefix = "getRetryStrategy()."; - - assertEquals(prefix + "getPolicy()", - in.getPolicy(), out.getPolicy()); - assertEquals(prefix + "getInitialBackoff()", - in.getInitialBackoff(), out.getInitialBackoff()); - assertEquals(prefix + "getMaximumBackoff()", - in.getMaximumBackoff(), out.getMaximumBackoff()); - } - - static void assertBundlesEqual(Bundle inExtras, Bundle outExtras) { - if (inExtras == null || outExtras == null) { - assertNull(inExtras); - assertNull(outExtras); - return; - } - - assertEquals("getExtras().size()", inExtras.size(), outExtras.size()); - final Set inKeys = inExtras.keySet(); - for (String key : inKeys) { - assertTrue("getExtras().containsKey(\"" + key + "\")", outExtras.containsKey(key)); - assertEquals("getExtras().get(\"" + key + "\")", inExtras.get(key), outExtras.get(key)); - } - } - - static void assertTriggersEqual(JobTrigger inTrigger, JobTrigger outTrigger) { - assertEquals("", inTrigger.getClass(), outTrigger.getClass()); - - if (inTrigger instanceof JobTrigger.ExecutionWindowTrigger) { - assertEquals("getTrigger().getWindowStart()", - ((JobTrigger.ExecutionWindowTrigger) inTrigger).getWindowStart(), - ((JobTrigger.ExecutionWindowTrigger) outTrigger).getWindowStart()); - assertEquals("getTrigger().getWindowEnd()", - ((JobTrigger.ExecutionWindowTrigger) inTrigger).getWindowEnd(), - ((JobTrigger.ExecutionWindowTrigger) outTrigger).getWindowEnd()); - } else if (inTrigger == Trigger.NOW) { - assertEquals(inTrigger, outTrigger); - } else if (inTrigger instanceof JobTrigger.ContentUriTrigger) { - assertEquals("Collection of URIs", - ((ContentUriTrigger) inTrigger).getUris(), - ((ContentUriTrigger) outTrigger).getUris()); - } else { - fail("Unknown Trigger class: " + inTrigger.getClass()); - } - } - - @NonNull - public static Builder getBuilderWithNoopValidator() { - return new Builder(new ValidationEnforcer(new NoopJobValidator())); - } - - @NonNull - static Bundle encodeContentUriJob(ContentUriTrigger trigger, JobCoder coder) { - Job job = getBuilderWithNoopValidator() - .setTag(TAG) - .setTrigger(trigger) - .setService(TestJobService.class) - .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) - .build(); - return coder.encode(job, new Bundle()); - } - - @NonNull - static Bundle encodeRecurringContentUriJob(ContentUriTrigger trigger, JobCoder coder) { - Job job = getBuilderWithNoopValidator() - .setTag(TAG) - .setTrigger(trigger) - .setService(TestJobService.class) - .setReplaceCurrent(true) - .setRecurring(true) - .build(); - return coder.encode(job, new Bundle()); - } - - static ContentUriTrigger getContentUriTrigger() { - ObservedUri contactUri = new ObservedUri( - ContactsContract.AUTHORITY_URI, Flags.FLAG_NOTIFY_FOR_DESCENDANTS); - ObservedUri imageUri = new ObservedUri(Media.EXTERNAL_CONTENT_URI, 0); - return Trigger.contentUriTrigger(Arrays.asList(contactUri, imageUri)); - } - - public static class TransactionArguments { - public final int code; - public final Parcel data; - public final int flags; - - public TransactionArguments(int code, Parcel data, int flags) { - this.code = code; - this.data = data; - this.flags = flags; - } - } - - public static class InspectableBinder extends Binder { - private final List transactionArguments = new LinkedList<>(); - - public InspectableBinder() {} - - @Override - protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) { - transactionArguments.add(new TransactionArguments(code, copyParcel(data), flags)); - return true; - } - - public PendingCallback toPendingCallback() { - Parcel container = Parcel.obtain(); - try { - container.writeStrongBinder(this); - container.setDataPosition(0); - return new PendingCallback(container); - } finally { - container.recycle(); - } - } - - private Parcel copyParcel(Parcel data) { - Parcel clone = Parcel.obtain(); - clone.appendFrom(data, 0, data.dataSize()); - clone.setDataPosition(0); - return clone; - } - - public List getArguments() { - return Collections.unmodifiableList(transactionArguments); - } - } - - private static class JobInvocationBuilder implements - JobParameterBuilder { - - @Override - public JobInvocation build(String tag, boolean replaceCurrent, List constraintList, - boolean recurring, int lifetime, JobTrigger trigger, Class service, - Bundle extras, RetryStrategy rs) { - //noinspection WrongConstant - return new JobInvocation.Builder() - .setTag(tag) - .setReplaceCurrent(replaceCurrent) - .setRecurring(recurring) - .setConstraints(toIntArray(constraintList)) - .setLifetime(lifetime) - .setTrigger(trigger) - .setService(service.getName()) - .addExtras(extras) - .setRetryStrategy(rs) - .build(); - } - } - - private static class JobBuilder implements JobParameterBuilder { - - private final Builder builder; - - public JobBuilder(Builder builder){ - this.builder = builder; - } - - @Override - public Job build(String tag, boolean replaceCurrent, List constraintList, - boolean recurring, int lifetime, JobTrigger trigger, Class service, - Bundle extras, RetryStrategy rs) { - //noinspection WrongConstant - return builder - .setTag(tag) - .setReplaceCurrent(replaceCurrent) - .setRecurring(recurring) - .setConstraints(toIntArray(constraintList)) - .setLifetime(lifetime) - .setTrigger(trigger) - .setService(service) - .setExtras(extras) - .setRetryStrategy(rs) - .build(); - } - } - - private interface JobParameterBuilder { - - T build(String tag, boolean replaceCurrent, List constraintList, boolean recurring, - int lifetime, JobTrigger trigger, Class service, Bundle extras, - RetryStrategy rs); - } -} diff --git a/jobdispatcher/src/testLib/java/com/google/android/gms/gcm/PendingCallback.java b/jobdispatcher/src/testLib/java/com/google/android/gms/gcm/PendingCallback.java deleted file mode 100644 index a12317c8a..000000000 --- a/jobdispatcher/src/testLib/java/com/google/android/gms/gcm/PendingCallback.java +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2016 Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -package com.google.android.gms.gcm; - -import android.os.IBinder; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.Keep; - -/** - * Parcelable class to wrap the binder we send to the client over IPC. Only included for the benefit - * of tests. - */ -@Keep -public final class PendingCallback implements Parcelable { - public static final Creator CREATOR = - new Creator() { - @Override - public PendingCallback createFromParcel(Parcel parcel) { - return new PendingCallback(parcel); - } - - @Override - public PendingCallback[] newArray(int i) { - return new PendingCallback[i]; - } - }; - private final IBinder mBinder; - - public PendingCallback(Parcel in) { - mBinder = in.readStrongBinder(); - } - - public IBinder getIBinder() { - return mBinder; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeStrongBinder(mBinder); - } -} diff --git a/settings.gradle b/settings.gradle index 570872419..e7b4def49 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':jobdispatcher' +include ':app' From 2fa39454d8d694cafe12dfc51ed57421bc800555 Mon Sep 17 00:00:00 2001 From: Apply55gx Date: Sun, 1 Oct 2017 15:09:02 +0200 Subject: [PATCH 16/36] Changed "Orgs" to "Organisation" For better understanding --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e437c556..cd288188b 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Yet another **open-source** GitHub client app but unlike any other app, FastHub - Edit Gist & Gist Files - React to Commit comments with reactions - Comment on line number in Files/Code changes. -- **Orgs** +- **Organisations** - Overview - Feeds - Teams & Teams repos From 051e47de3cf64b71a2262811168fb8bb8b74c133 Mon Sep 17 00:00:00 2001 From: cozyplanes Date: Sun, 1 Oct 2017 23:00:58 +0900 Subject: [PATCH 17/36] Update strings.xml Korean translation update for changes made in 4.4.0 Changes complete 1 out of 4 (1/4) --- app/src/main/res/values-ko/strings.xml | 32 +++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index e291578a5..db87efb20 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -433,6 +433,36 @@ %s %s가 추가함 변경사항이 너무 많습니다. 브라우저로 보세요 프로젝트 - 읽어주세요 + 꼭 읽어주세요 자주하는 질문 + 댓글을 성공적으로 추가하였습니다. + + • 왜 Private 이나 Public으로 되어진 제 Organizations 을 볼 수 없나요? +

Open up https://github.com/settings/applications 를 열고 Fasthub라는 것을 찾아보세요. 그것을 열고, Organization access 부분에서 Grant를 눌러주세요. 또는 초기 로그인시 엑세스 토큰 으로 로그인하면 편리합니다.

+
• Access Token & OTP 으로 로그인을 시도했는데 로그인이 되질 않아요...
+

OTP는 일회용 코드입니다. 따리서 유효기간에 인해 엑세스 토큰 + OTP로 로그인이 불가합니다. 이렇게 로그인을 하게 되면 몇 초에 한 번씩 앱이 로그인을 하라고 합니다. 다른 방법을 사용해 주세요.

+
• 제 비공개 리포지토리와 엔터프라이즈 위키가 보이질 않아요!
+

위 두 정보를 스크랩하려면 특별한 세션 토큰이 필요한데 본 앱을 이 토큰을 받을 수 없습니다. 깃허브 API의 한계이므로 API문의는 깃허브에게 해주세요. 본 앱의 개발자는 API문의를 받지 않습니다.

+
• 제가 엔터프라이즈 계정으로 로그인했는데 Enterprise GitHub 외의 다른 것들은 볼 수 없나요?
\n +

결론부터 말하자면 불가능합니다. 본 앱은 여러분이 다른 것들을 볼 수 있게 해 주고 있지만, 기술적 한계로 크게 도움을 드릴 수는 없습니다. 대부분의 경우에는 여러분의 로그인 계정이 깃허브 서버에 상주하고 있기 때문입니다. 그러나 소수/b>의 경우에는 여러분의 OAuth 토큰이 다른 활동을 하게 해 줄 수 있습니다.

+
• Why am I having problems editing Issues/PRs?
+

If you are editing a public Org repo, then please contact your Org to grant access to FastHub or use Access Token to login!.

+
• I\'m having this issue or I want this & that!!
+

Head to https://github.com/k0shk0sh/FastHub/issues/new and create new issue for bugs or feature requests, I really do encourage you to + search before opening a ticket. Any duplicate request will result in it being closed immediately.

+
• How do I get PROMO CODE?
+

If you are a student, you\'ll have to provide me via Email that you are student, you will need below documents:

+
    +
  • Your university identity card & your identity card (that shows your name & your face to compare it!)
  • +
  • Your university start & end date
  • +
  • Rate FastHub in the Play Store
  • +
+

If you aren\'t a student and you can\'t afford to pay for PRO, you\'ll need:

+
    +
  • Write an article about FastHub in social media such as (Medium)
  • +
  • Rate FastHub in the Play Store
  • +
+
본 한국어 번역자는 이 앱의 개발에 참여하지 않았습니다. 따라서 기술적 문의는 이 앱의 개발자인 Kosh Sergani에게 해주시기 바랍니다. 본 번역이 앱 사용에 도움이 되셨기를 바랍니다. 감사합니다.
+ From 6f6fb9d38e49d672eebf44db50d429a3bebe2bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Segovia=20C=C3=B3rdoba?= Date: Sun, 1 Oct 2017 16:27:49 +0200 Subject: [PATCH 18/36] Update strings.xml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Tenés" is not used in Spain and "Repórtalo" has accent mark. --- app/src/main/res/values-es/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 0baed60c1..ed9932dda 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -217,10 +217,10 @@ Autor Fork en GitHub Enviar un email - ¿Tenés preguntas sobre FastHub? + ¿Tienes preguntas sobre FastHub? Comentarios Reportar un error - ¿Tenés un problema? Reportalo aquí + ¿Tienes un problema? Repórtalo aquí Acerca de Notificación Apagar From 365d13ad68826ed4fd74291ee3f456f4bb506041 Mon Sep 17 00:00:00 2001 From: Yakov Date: Sun, 1 Oct 2017 13:42:27 -0400 Subject: [PATCH 19/36] Fixed #956 and a small issue template correction --- .../provider/markdown/CachedComments.kt | 4 +--- .../viewholder/PullRequestEventViewHolder.kt | 4 ++-- .../viewholder/UnknownTypeViewHolder.kt | 2 +- .../com/fastaccess/ui/base/MainNavDrawer.kt | 6 ++--- .../modules/editor/emoji/EmojiBottomSheet.kt | 6 ++--- .../main/donation/CheckPurchaseActivity.kt | 2 +- .../repos/extras/branches/BranchesFragment.kt | 6 ++--- .../branches/pager/BranchesPagerFragment.kt | 6 ++--- .../delete/DeleteFileBottomSheetFragment.kt | 5 ++-- .../projects/RepoProjectsFragmentPager.kt | 2 +- .../projects/columns/ProjectColumnFragment.kt | 2 +- .../reviews/changes/ReviewChangesActivity.kt | 2 +- .../sound/NotificationSoundBottomSheet.kt | 6 ++--- .../ui/modules/trending/TrendingActivity.kt | 24 +++++++++---------- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 16 files changed, 39 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/com/fastaccess/provider/markdown/CachedComments.kt b/app/src/main/java/com/fastaccess/provider/markdown/CachedComments.kt index d84591524..87a90ee19 100644 --- a/app/src/main/java/com/fastaccess/provider/markdown/CachedComments.kt +++ b/app/src/main/java/com/fastaccess/provider/markdown/CachedComments.kt @@ -17,9 +17,7 @@ class CachedComments private constructor() { return map["$repo/$login/$number"] } - fun clear() { - map.clear() - } + fun clear() = map.clear() private object Holder { val INSTANCE = CachedComments() diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestEventViewHolder.kt b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestEventViewHolder.kt index 6b29ed9e6..6e2bfb6f9 100644 --- a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestEventViewHolder.kt +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestEventViewHolder.kt @@ -402,8 +402,8 @@ class PullRequestEventViewHolder private constructor(view: View, adapter: BaseRe if (value == null) { return "" } - if (value.length <= 7) return value - else return value.substring(0, 7) + return if (value.length <= 7) value + else value.substring(0, 7) } companion object { diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/UnknownTypeViewHolder.kt b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/UnknownTypeViewHolder.kt index 6fe1a3cfb..3fa035890 100644 --- a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/UnknownTypeViewHolder.kt +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/UnknownTypeViewHolder.kt @@ -6,6 +6,6 @@ import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder /** * Created by kosh on 07/08/2017. */ -class UnknownTypeViewHolder(private val view: View) : BaseViewHolder(view) { +class UnknownTypeViewHolder(view: View) : BaseViewHolder(view) { override fun bind(t: Any) {} //DO NOTHING } \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/base/MainNavDrawer.kt b/app/src/main/java/com/fastaccess/ui/base/MainNavDrawer.kt index 1bd5b451f..238e55a9e 100644 --- a/app/src/main/java/com/fastaccess/ui/base/MainNavDrawer.kt +++ b/app/src/main/java/com/fastaccess/ui/base/MainNavDrawer.kt @@ -39,13 +39,13 @@ class MainNavDrawer(val view: BaseActivity<*, *>, private val extraNav: Navigati : BaseViewHolder.OnItemClickListener { private var menusHolder: ViewGroup? = null - private val togglePinned: View? = view.findViewById(R.id.togglePinned) - private val pinnedList: DynamicRecyclerView? = view.findViewById(R.id.pinnedList) + private val togglePinned: View? = view.findViewById(R.id.togglePinned) + private val pinnedList: DynamicRecyclerView? = view.findViewById(R.id.pinnedList) private val pinnedListAdapter = PinnedReposAdapter(true) private val userModel: Login? = Login.getUser() init { - menusHolder = view.findViewById(R.id.menusHolder) + menusHolder = view.findViewById(R.id.menusHolder) pinnedListAdapter.listener = object : BaseViewHolder.OnItemClickListener { override fun onItemClick(position: Int, v: View?, item: PinnedRepos?) { if (v != null && item != null) { diff --git a/app/src/main/java/com/fastaccess/ui/modules/editor/emoji/EmojiBottomSheet.kt b/app/src/main/java/com/fastaccess/ui/modules/editor/emoji/EmojiBottomSheet.kt index 3e46961f6..4e06b396b 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/editor/emoji/EmojiBottomSheet.kt +++ b/app/src/main/java/com/fastaccess/ui/modules/editor/emoji/EmojiBottomSheet.kt @@ -34,9 +34,9 @@ class EmojiBottomSheet : BaseMvpBottomSheetDialogFragment emojiCallback = parentFragment as EmojiMvp.EmojiCallback - context is EmojiMvp.EmojiCallback -> emojiCallback = context + emojiCallback = when { + parentFragment is EmojiMvp.EmojiCallback -> parentFragment as EmojiMvp.EmojiCallback + context is EmojiMvp.EmojiCallback -> context else -> throw IllegalArgumentException("${context.javaClass.simpleName} must implement EmojiMvp.EmojiCallback") } } diff --git a/app/src/main/java/com/fastaccess/ui/modules/main/donation/CheckPurchaseActivity.kt b/app/src/main/java/com/fastaccess/ui/modules/main/donation/CheckPurchaseActivity.kt index 2948d88c1..23db135d7 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/main/donation/CheckPurchaseActivity.kt +++ b/app/src/main/java/com/fastaccess/ui/modules/main/donation/CheckPurchaseActivity.kt @@ -38,7 +38,7 @@ class CheckPurchaseActivity : Activity() { } } - override fun onBackPressed() {} + override fun onBackPressed() = Unit private fun startMainActivity() { startActivity(Intent(this, MainActivity::class.java)) diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/extras/branches/BranchesFragment.kt b/app/src/main/java/com/fastaccess/ui/modules/repos/extras/branches/BranchesFragment.kt index 1ffed6abb..96c929f16 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/extras/branches/BranchesFragment.kt +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/extras/branches/BranchesFragment.kt @@ -34,9 +34,9 @@ class BranchesFragment : BaseFragment(), Br override fun onAttach(context: Context) { super.onAttach(context) - if (parentFragment is BranchesPagerListener) { - branchCallback = parentFragment as BranchesPagerListener - } else branchCallback = context as BranchesPagerListener + branchCallback = if (parentFragment is BranchesPagerListener) { + parentFragment as BranchesPagerListener + } else context as BranchesPagerListener } override fun onDetach() { diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/extras/branches/pager/BranchesPagerFragment.kt b/app/src/main/java/com/fastaccess/ui/modules/repos/extras/branches/pager/BranchesPagerFragment.kt index ecf94306e..5eb08b4ca 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/extras/branches/pager/BranchesPagerFragment.kt +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/extras/branches/pager/BranchesPagerFragment.kt @@ -31,9 +31,9 @@ class BranchesPagerFragment : BaseDialogFragment(), Tr } override fun onOptionsItemSelected(item: MenuItem?): Boolean { - when (item?.itemId) { + return when (item?.itemId) { R.id.menu -> { drawerLayout.openDrawer(Gravity.END) - return true + true } android.R.id.home -> { startActivity(Intent(this, MainActivity::class.java)) finish() - return true + true } - else -> return super.onOptionsItemSelected(item) + else -> super.onOptionsItemSelected(item) } } @@ -156,9 +156,9 @@ class TrendingActivity : BaseActivity(), Tr } private fun onItemClicked(item: MenuItem?): Boolean { - when (item?.title.toString()) { - "All Language" -> selectedTitle = "" - else -> selectedTitle = item?.title.toString() + selectedTitle = when (item?.title.toString()) { + "All Language" -> "" + else -> item?.title.toString() } Logger.e(selectedTitle) setValues() @@ -176,11 +176,11 @@ class TrendingActivity : BaseActivity(), Tr } private fun getSince(): String { - when { - daily.isSelected -> return "daily" - weekly.isSelected -> return "weekly" - monthly.isSelected -> return "monthly" - else -> return "daily" + return when { + daily.isSelected -> "daily" + weekly.isSelected -> "weekly" + monthly.isSelected -> "monthly" + else -> "daily" } } diff --git a/build.gradle b/build.gradle index 831bd8c31..f60a37cd9 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { assertjVersion = '2.5.0' espresseVersion = '2.2.2' requery = '1.3.2' - kotlin_version = '1.1.4-3' + kotlin_version = '1.1.50' commonmark = '0.9.0' } repositories { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 15eb24ec2..7d171b3b1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-all.zip android.enableD8=true \ No newline at end of file From 81a8cc3a2d5c4588a59a32f1ec4b06b656b7f2e8 Mon Sep 17 00:00:00 2001 From: Yakov Date: Sun, 1 Oct 2017 17:08:09 -0400 Subject: [PATCH 20/36] kotlin update --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f60a37cd9..d3b2f8204 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { assertjVersion = '2.5.0' espresseVersion = '2.2.2' requery = '1.3.2' - kotlin_version = '1.1.50' + kotlin_version = '1.1.51' commonmark = '0.9.0' } repositories { From d526db5846289b564f25ca68dabc7ca7b71793f0 Mon Sep 17 00:00:00 2001 From: maple Date: Mon, 2 Oct 2017 07:50:44 +0800 Subject: [PATCH 21/36] zh-rTW FAQ text fix --- app/src/main/res/values-zh-rTW/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 9ab4b395f..9a927f25d 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -449,8 +449,8 @@ APP 在新視窗中開啟 差異太多無法顯示。請於瀏覽器檢視它們 專案 - FAQ - + FAQ + • 為何我無法看到我的 組織私人 / 公開 的東西?

打開 https://github.com/settings/applications 並找到 FastHub, 點擊它並滑到 Organization access 並點擊 Grant 按鈕, From ed95b552ad716da7f6bb5a5d0c65e00a48fceb22 Mon Sep 17 00:00:00 2001 From: cozyplanes Date: Mon, 2 Oct 2017 14:21:15 +0900 Subject: [PATCH 22/36] Update strings.xml --- app/src/main/res/values-ko/strings.xml | 29 +++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index db87efb20..57503b5cd 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -439,30 +439,29 @@ • 왜 Private 이나 Public으로 되어진 제 Organizations 을 볼 수 없나요? -

Open up https://github.com/settings/applications 를 열고 Fasthub라는 것을 찾아보세요. 그것을 열고, Organization access 부분에서 Grant를 눌러주세요. 또는 초기 로그인시 엑세스 토큰 으로 로그인하면 편리합니다.

+

Open up https://github.com/settings/applications 를 열고 Fasthub라는 것을 찾아보세요. 그것을 열고, Organization access 부분에서 Grant를 눌러주세요. 또는 초기 로그인시 엑세스 토큰 으로 로그인하면 편리합니다.

\n
• Access Token & OTP 으로 로그인을 시도했는데 로그인이 되질 않아요...

OTP는 일회용 코드입니다. 따리서 유효기간에 인해 엑세스 토큰 + OTP로 로그인이 불가합니다. 이렇게 로그인을 하게 되면 몇 초에 한 번씩 앱이 로그인을 하라고 합니다. 다른 방법을 사용해 주세요.

• 제 비공개 리포지토리와 엔터프라이즈 위키가 보이질 않아요!

위 두 정보를 스크랩하려면 특별한 세션 토큰이 필요한데 본 앱을 이 토큰을 받을 수 없습니다. 깃허브 API의 한계이므로 API문의는 깃허브에게 해주세요. 본 앱의 개발자는 API문의를 받지 않습니다.

• 제가 엔터프라이즈 계정으로 로그인했는데 Enterprise GitHub 외의 다른 것들은 볼 수 없나요?
\n -

결론부터 말하자면 불가능합니다. 본 앱은 여러분이 다른 것들을 볼 수 있게 해 주고 있지만, 기술적 한계로 크게 도움을 드릴 수는 없습니다. 대부분의 경우에는 여러분의 로그인 계정이 깃허브 서버에 상주하고 있기 때문입니다. 그러나 소수/b>의 경우에는 여러분의 OAuth 토큰이 다른 활동을 하게 해 줄 수 있습니다.

-
• Why am I having problems editing Issues/PRs?
-

If you are editing a public Org repo, then please contact your Org to grant access to FastHub or use Access Token to login!.

-
• I\'m having this issue or I want this & that!!
-

Head to https://github.com/k0shk0sh/FastHub/issues/new and create new issue for bugs or feature requests, I really do encourage you to - search before opening a ticket. Any duplicate request will result in it being closed immediately.

-
• How do I get PROMO CODE?
-

If you are a student, you\'ll have to provide me via Email that you are student, you will need below documents:

+

결론부터 말하자면 불가능합니다. 본 앱은 여러분이 다른 것들을 볼 수 있게 해 주고 있지만, 기술적 한계로 크게 도움을 드릴 수는 없습니다. 대부분의 경우에는 여러분의 로그인 계정이 깃허브 서버에 상주하고 있기 때문입니다. 그러나 소수/b>의 경우에는 여러분의 OAuth 토큰이 다른 활동을 하게 해 줄 수 있습니다.

\n +
• Issues/PR 를 수정 하는데 문제가 있습니다.
+

공개 Organization 리포지토리를 수정하면, 여러분의 Organization에게 문의하여 Fasthub 권환을 받거나 최초 로그인시 엑세스 토큰으로 로그인하세요.

\n +
• 앱을 사용하는데 문제를 발견했어요! / 혹시 이러한 기능을 추가해주실 수 있나요?
+

그럼요! https://github.com/k0shk0sh/FastHub/issues/new 로 간 후 새로운 티켓을 만드세요. 티켓을 만들기 전 먼저 전에 있었던 티켓 중 중복되는 것이 없는지 검색해 주세요. 중복시 티켓은 바로 Close됩니다.

\n +
• PROMO CODE는 어떻게 받나요?
+

학생이신가요? 자신이 학생이라는 증명 서류가 필요합니다 :

    -
  • Your university identity card & your identity card (that shows your name & your face to compare it!)
  • -
  • Your university start & end date
  • +
  • 자신의 대학(학교) 학생증 & 신분증 복사본 (이름 및 얼굴 대조를 위한 것 입니다. 관련 서류는 인증 후 폐기됩니다.)
  • +
  • 자신의 대학(학교)의 시작일 및 졸업일
  • Rate FastHub in the Play Store
-

If you aren\'t a student and you can\'t afford to pay for PRO, you\'ll need:

+

자신의 학생이 아니지만 PRO를 구매하실 수 없다구요?

    -
  • Write an article about FastHub in social media such as (Medium)
  • +
  • SNS에 Fasthub에 대한해 추천 글 및 평가를 올려주세요.
  • Rate FastHub in the Play Store
-
본 한국어 번역자는 이 앱의 개발에 참여하지 않았습니다. 따라서 기술적 문의는 이 앱의 개발자인 Kosh Sergani에게 해주시기 바랍니다. 본 번역이 앱 사용에 도움이 되셨기를 바랍니다. 감사합니다.
- +
위 조건을 만족시킨 후 본 앱의 개발자인 Kosh Sergani에게 이메일을 보내면 검토후 PROMO CODE를 보내 드립니다.
\n +
본 한국어 번역자는 이 앱의 개발에 참여하지 않았습니다. 따라서 기술적 문의는 이 앱의 개발자인 Kosh Sergani에게 해주시기 바랍니다. 본 번역이 앱 사용에 도움이 되셨기를 바랍니다. 감사합니다.
From 8bf4482018da9702261e88c48d5e7bee62c309f7 Mon Sep 17 00:00:00 2001 From: cozyplanes Date: Mon, 2 Oct 2017 14:35:46 +0900 Subject: [PATCH 23/36] Update strings.xml --- app/src/main/res/values-ko/strings.xml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 57503b5cd..3ad77dc4f 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -439,9 +439,9 @@ • 왜 Private 이나 Public으로 되어진 제 Organizations 을 볼 수 없나요? -

Open up https://github.com/settings/applications 를 열고 Fasthub라는 것을 찾아보세요. 그것을 열고, Organization access 부분에서 Grant를 눌러주세요. 또는 초기 로그인시 엑세스 토큰 으로 로그인하면 편리합니다.

\n -
• Access Token & OTP 으로 로그인을 시도했는데 로그인이 되질 않아요...
-

OTP는 일회용 코드입니다. 따리서 유효기간에 인해 엑세스 토큰 + OTP로 로그인이 불가합니다. 이렇게 로그인을 하게 되면 몇 초에 한 번씩 앱이 로그인을 하라고 합니다. 다른 방법을 사용해 주세요.

+

Open up https://github.com/settings/applications 를 열고 Fasthub라는 것을 찾아보세요. 그것을 선택한 후, Organization Access 부분에서 Grant를 눌러주세요. 또는 초기 로그인시 엑세스 토큰 으로 로그인하면 편리합니다.

\n +
• Access Token 과 OTP 으로 로그인을 시도했는데 로그인이 되질 않아요...
+

OTP는 일회용 코드입니다. 따리서 유효기간에 인해 엑세스 토큰 + OTP로 로그인이 불가합니다. 이렇게 로그인을 하게 되면 몇 초에 한 번씩 앱이 로그인을 하라고 합니다. 다른 방법을 사용해 주세요.

\n
• 제 비공개 리포지토리와 엔터프라이즈 위키가 보이질 않아요!

위 두 정보를 스크랩하려면 특별한 세션 토큰이 필요한데 본 앱을 이 토큰을 받을 수 없습니다. 깃허브 API의 한계이므로 API문의는 깃허브에게 해주세요. 본 앱의 개발자는 API문의를 받지 않습니다.

• 제가 엔터프라이즈 계정으로 로그인했는데 Enterprise GitHub 외의 다른 것들은 볼 수 없나요?
\n @@ -449,17 +449,14 @@
• Issues/PR 를 수정 하는데 문제가 있습니다.

공개 Organization 리포지토리를 수정하면, 여러분의 Organization에게 문의하여 Fasthub 권환을 받거나 최초 로그인시 엑세스 토큰으로 로그인하세요.

\n
• 앱을 사용하는데 문제를 발견했어요! / 혹시 이러한 기능을 추가해주실 수 있나요?
-

그럼요! https://github.com/k0shk0sh/FastHub/issues/new 로 간 후 새로운 티켓을 만드세요. 티켓을 만들기 전 먼저 전에 있었던 티켓 중 중복되는 것이 없는지 검색해 주세요. 중복시 티켓은 바로 Close됩니다.

\n +

그럼요! https://github.com/k0shk0sh/FastHub/issues/new 로 간 후 새로운 티켓을 만드세요. 티켓을 만들기 전 먼저 전에 있었던 티켓 중 중복되는 것이 없는지 검색해 주세요. 중복시 티켓은 바로 닫힙니다.

\n
• PROMO CODE는 어떻게 받나요?
-

학생이신가요? 자신이 학생이라는 증명 서류가 필요합니다 :

-
    -
  • 자신의 대학(학교) 학생증 & 신분증 복사본 (이름 및 얼굴 대조를 위한 것 입니다. 관련 서류는 인증 후 폐기됩니다.)
  • +
  • 학생이시면 자신의 대학(학교) 학생증 & 신분증 복사본이 필요합니다. (이름 및 얼굴 대조를 위한 것 입니다. 관련 서류는 인증 후 폐기됩니다.)
  • 자신의 대학(학교)의 시작일 및 졸업일
  • Rate FastHub in the Play Store
-

자신의 학생이 아니지만 PRO를 구매하실 수 없다구요?

    -
  • SNS에 Fasthub에 대한해 추천 글 및 평가를 올려주세요.
  • +
  • 학생이 아니지만 PRO버전을 구매하실 수 없다면 SNS에 Fasthub에 대한 추천 글 및 평가를 올려주세요.
  • Rate FastHub in the Play Store
위 조건을 만족시킨 후 본 앱의 개발자인 Kosh Sergani에게 이메일을 보내면 검토후 PROMO CODE를 보내 드립니다.
\n From 691f4f23ebb1f308ba2713576db53504ebf1795e Mon Sep 17 00:00:00 2001 From: cozyplanes Date: Mon, 2 Oct 2017 15:58:27 +0900 Subject: [PATCH 24/36] Update strings.xml --- app/src/main/res/values-ko/strings.xml | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 3ad77dc4f..2af1254df 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -439,26 +439,29 @@ • 왜 Private 이나 Public으로 되어진 제 Organizations 을 볼 수 없나요? -

Open up https://github.com/settings/applications 를 열고 Fasthub라는 것을 찾아보세요. 그것을 선택한 후, Organization Access 부분에서 Grant를 눌러주세요. 또는 초기 로그인시 엑세스 토큰 으로 로그인하면 편리합니다.

\n +

Open up https://github.com/settings/applications 를 열고 Fasthub라는 것을 찾아보세요. 그것을 선택한 후, Organization Access 부분에서 Grant를 눌러주세요. 또는 초기 로그인시 엑세스 토큰 으로 로그인하면 편리합니다.

• Access Token 과 OTP 으로 로그인을 시도했는데 로그인이 되질 않아요...
-

OTP는 일회용 코드입니다. 따리서 유효기간에 인해 엑세스 토큰 + OTP로 로그인이 불가합니다. 이렇게 로그인을 하게 되면 몇 초에 한 번씩 앱이 로그인을 하라고 합니다. 다른 방법을 사용해 주세요.

\n +

OTP는 일회용 코드입니다. 따리서 유효기간에 인해 엑세스 토큰 + OTP로 로그인이 불가합니다. 이렇게 로그인을 하게 되면 몇 초에 한 번씩 앱이 로그인을 하라고 합니다. 다른 방법을 사용해 주세요.

• 제 비공개 리포지토리와 엔터프라이즈 위키가 보이질 않아요!

위 두 정보를 스크랩하려면 특별한 세션 토큰이 필요한데 본 앱을 이 토큰을 받을 수 없습니다. 깃허브 API의 한계이므로 API문의는 깃허브에게 해주세요. 본 앱의 개발자는 API문의를 받지 않습니다.

-
• 제가 엔터프라이즈 계정으로 로그인했는데 Enterprise GitHub 외의 다른 것들은 볼 수 없나요?
\n -

결론부터 말하자면 불가능합니다. 본 앱은 여러분이 다른 것들을 볼 수 있게 해 주고 있지만, 기술적 한계로 크게 도움을 드릴 수는 없습니다. 대부분의 경우에는 여러분의 로그인 계정이 깃허브 서버에 상주하고 있기 때문입니다. 그러나 소수/b>의 경우에는 여러분의 OAuth 토큰이 다른 활동을 하게 해 줄 수 있습니다.

\n +
• 제가 엔터프라이즈 계정으로 로그인했는데 Enterprise GitHub 외의 다른 것들은 볼 수 없나요?
+

결론부터 말하자면 불가능합니다. 본 앱은 여러분이 다른 것들을 볼 수 있게 해 주고 있지만, 기술적 한계로 크게 도움을 드릴 수는 없습니다. 대부분의 경우에는 여러분의 로그인 계정이 깃허브 서버에 상주하고 있기 때문입니다. 그러나 소수/b>의 경우에는 여러분의 OAuth 토큰이 다른 활동을 하게 해 줄 수 있습니다.

• Issues/PR 를 수정 하는데 문제가 있습니다.
-

공개 Organization 리포지토리를 수정하면, 여러분의 Organization에게 문의하여 Fasthub 권환을 받거나 최초 로그인시 엑세스 토큰으로 로그인하세요.

\n +

공개 Organization 리포지토리를 수정하면, 여러분의 Organization에게 문의하여 Fasthub 권환을 받거나 최초 로그인시 엑세스 토큰으로 로그인하세요.

• 앱을 사용하는데 문제를 발견했어요! / 혹시 이러한 기능을 추가해주실 수 있나요?
-

그럼요! https://github.com/k0shk0sh/FastHub/issues/new 로 간 후 새로운 티켓을 만드세요. 티켓을 만들기 전 먼저 전에 있었던 티켓 중 중복되는 것이 없는지 검색해 주세요. 중복시 티켓은 바로 닫힙니다.

\n +

그럼요! https://github.com/k0shk0sh/FastHub/issues/new 로 간 후 새로운 티켓을 만드세요. 티켓을 만들기 전 먼저 전에 있었던 티켓 중 중복되는 것이 없는지 검색해 주세요. 중복시 티켓은 바로 닫힙니다.

• PROMO CODE는 어떻게 받나요?
-
  • 학생이시면 자신의 대학(학교) 학생증 & 신분증 복사본이 필요합니다. (이름 및 얼굴 대조를 위한 것 입니다. 관련 서류는 인증 후 폐기됩니다.)
  • +

    학생인 경우:

    +
  • 자신의 대학(학교) 학생증 & 신분증 복사본 (이름 및 얼굴 대조를 위한 것 입니다. 관련 서류는 인증 후 폐기됩니다.)
  • 자신의 대학(학교)의 시작일 및 졸업일
  • -
  • Rate FastHub in the Play Store
  • +
  • 플레이 스토어에서 FastHub 별점 매기기
    • -
    • 학생이 아니지만 PRO버전을 구매하실 수 없다면 SNS에 Fasthub에 대한 추천 글 및 평가를 올려주세요.
    • -
    • Rate FastHub in the Play Store
    • +

      학생이 아니지만 PRO버전을 구매하실 수 없는 경우:

      +
    • SNS에 Fasthub에 대한 추천 글 및 평가글 올리기.
    • +
    • 플레이 스토어에서 FastHub 별점 매기기
    위 조건을 만족시킨 후 본 앱의 개발자인 Kosh Sergani에게 이메일을 보내면 검토후 PROMO CODE를 보내 드립니다.
    \n -
    본 한국어 번역자는 이 앱의 개발에 참여하지 않았습니다. 따라서 기술적 문의는 이 앱의 개발자인 Kosh Sergani에게 해주시기 바랍니다. 본 번역이 앱 사용에 도움이 되셨기를 바랍니다. 감사합니다.
    +
    본 한국어 번역자는 이 앱의 개발에 참여하지 않았습니다. 따라서 기술적 문의는 이 앱의 개발자인 Kosh Sergani에게 해주시기 바랍니다. 본 번역이 앱 사용에 도움이 되셨기를 바랍니다. 감사합니다.
    + ]]>
    From a9ea6a4bb21ed4d689d321041791864b98780e5f Mon Sep 17 00:00:00 2001 From: cozyplanes Date: Mon, 2 Oct 2017 16:01:34 +0900 Subject: [PATCH 25/36] Possible newline typo Possible newline typo, not sure --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1a5c453ff..ee862b9c8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -581,7 +581,7 @@
    • Why are my Private Repo & Enterprise Wiki not showing up?

    It\'s due to FastHub scraping GitHub Wiki page & Private Repos require session token that FastHub is unable to obtain.

    -
    • I login with Enterprise account but can\'t interact with anything other than my Enterprise GitHub?
    \n +
    • I login with Enterprise account but can\'t interact with anything other than my Enterprise GitHub?

    Well, logically, you can\'t access anything else other than your Enterprise. FastHub tries to allow as much possible, but can\'t do much about it in most cases, since your login credential doesn\'t exists in GitHub server. But in a few cases your GitHub account Oauth token will do the trick.

    From 2302f02396c5a173b223dec36e0888d97720bf9b Mon Sep 17 00:00:00 2001 From: k0shk0sh Date: Tue, 3 Oct 2017 06:50:35 +0200 Subject: [PATCH 26/36] this commit fixes #657 --- .../fastaccess/data/dao/CreateIssueModel.java | 13 +- .../create/MilestoneDialogFragment.java | 11 +- .../issues/create/CreateIssueActivity.java | 117 +++++++++++++- .../repos/issues/create/CreateIssueMvp.java | 22 ++- .../issues/create/CreateIssuePresenter.java | 77 ++++++++- .../layout/create_issue_layout.xml | 150 +++++++++++++++++- .../layout/issue_popup_layout.xml | 4 +- 7 files changed, 377 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/fastaccess/data/dao/CreateIssueModel.java b/app/src/main/java/com/fastaccess/data/dao/CreateIssueModel.java index d1f82c123..97fddc879 100644 --- a/app/src/main/java/com/fastaccess/data/dao/CreateIssueModel.java +++ b/app/src/main/java/com/fastaccess/data/dao/CreateIssueModel.java @@ -3,6 +3,8 @@ import android.os.Parcel; import android.os.Parcelable; +import java.util.ArrayList; + import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -15,17 +17,26 @@ public class CreateIssueModel implements Parcelable { private String title; private String body; + private ArrayList labels; + private ArrayList assignees; + private MilestoneModel milestone; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.title); dest.writeString(this.body); + dest.writeStringList(this.labels); + dest.writeStringList(this.assignees); + dest.writeParcelable(this.milestone, flags); } - @SuppressWarnings("WeakerAccess") protected CreateIssueModel(Parcel in) { + protected CreateIssueModel(Parcel in) { this.title = in.readString(); this.body = in.readString(); + this.labels = in.createStringArrayList(); + this.assignees = in.createStringArrayList(); + this.milestone = in.readParcelable(MilestoneModel.class.getClassLoader()); } public static final Creator CREATOR = new Creator() { diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/extras/milestone/create/MilestoneDialogFragment.java b/app/src/main/java/com/fastaccess/ui/modules/repos/extras/milestone/create/MilestoneDialogFragment.java index 7d2a8fbcd..05afccb92 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/extras/milestone/create/MilestoneDialogFragment.java +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/extras/milestone/create/MilestoneDialogFragment.java @@ -26,6 +26,7 @@ public class MilestoneDialogFragment extends BaseDialogFragment implements Miles private IssuePagerMvp.View issueCallback; private PullRequestPagerMvp.View pullRequestCallback; + private MilestoneMvp.OnMilestoneSelected milestoneCallback; public static MilestoneDialogFragment newInstance(@NonNull String login, @NonNull String repo) { MilestoneDialogFragment view = new MilestoneDialogFragment(); @@ -48,6 +49,12 @@ public static MilestoneDialogFragment newInstance(@NonNull String login, @NonNul } else if (getParentFragment() instanceof PullRequestPagerMvp.View) { pullRequestCallback = (PullRequestPagerMvp.View) getParentFragment(); } + + if (context instanceof MilestoneMvp.OnMilestoneSelected) { + milestoneCallback = (MilestoneMvp.OnMilestoneSelected) context; + } else if (getParentFragment() instanceof MilestoneMvp.OnMilestoneSelected) { + milestoneCallback = (MilestoneMvp.OnMilestoneSelected) getParentFragment(); + } } @Override public void onDetach() { @@ -65,7 +72,8 @@ public static MilestoneDialogFragment newInstance(@NonNull String login, @NonNul @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { if (savedInstanceState == null) { Bundle bundle = getArguments(); - com.fastaccess.ui.modules.repos.extras.milestone.MilestoneDialogFragment milestoneView = new com.fastaccess.ui.modules.repos.extras.milestone.MilestoneDialogFragment(); + com.fastaccess.ui.modules.repos.extras.milestone.MilestoneDialogFragment milestoneView = new com.fastaccess.ui.modules.repos.extras + .milestone.MilestoneDialogFragment(); milestoneView.setArguments(bundle); getChildFragmentManager() .beginTransaction() @@ -77,5 +85,6 @@ public static MilestoneDialogFragment newInstance(@NonNull String login, @NonNul @Override public void onMilestoneSelected(@NonNull MilestoneModel milestoneModel) { if (issueCallback != null) issueCallback.onMileStoneSelected(milestoneModel); if (pullRequestCallback != null) pullRequestCallback.onMileStoneSelected(milestoneModel); + if (milestoneCallback != null) milestoneCallback.onMilestoneSelected(milestoneModel); } } diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssueActivity.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssueActivity.java index 75f5de0da..8a82c4251 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssueActivity.java +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssueActivity.java @@ -3,34 +3,49 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.graphics.Color; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.TextInputLayout; +import android.support.transition.TransitionManager; import android.support.v4.app.Fragment; import android.support.v7.app.AlertDialog; import android.view.MotionEvent; import android.view.View; +import android.widget.LinearLayout; import com.danielstone.materialaboutlibrary.ConvenienceBuilder; import com.evernote.android.state.State; import com.fastaccess.App; import com.fastaccess.BuildConfig; import com.fastaccess.R; +import com.fastaccess.data.dao.LabelListModel; +import com.fastaccess.data.dao.LabelModel; +import com.fastaccess.data.dao.MilestoneModel; import com.fastaccess.data.dao.model.Issue; import com.fastaccess.data.dao.model.PullRequest; +import com.fastaccess.data.dao.model.User; import com.fastaccess.helper.ActivityHelper; import com.fastaccess.helper.AppHelper; import com.fastaccess.helper.BundleConstant; import com.fastaccess.helper.Bundler; import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.Logger; import com.fastaccess.helper.ViewHelper; import com.fastaccess.provider.markdown.MarkDownProvider; import com.fastaccess.ui.base.BaseActivity; import com.fastaccess.ui.modules.editor.EditorActivity; +import com.fastaccess.ui.modules.repos.extras.assignees.AssigneesDialogFragment; +import com.fastaccess.ui.modules.repos.extras.labels.LabelsDialogFragment; +import com.fastaccess.ui.modules.repos.extras.milestone.create.MilestoneDialogFragment; import com.fastaccess.ui.widgets.FontTextView; +import com.fastaccess.ui.widgets.LabelSpan; +import com.fastaccess.ui.widgets.SpannableBuilder; import com.fastaccess.ui.widgets.dialog.MessageDialogView; +import java.util.ArrayList; + import butterknife.BindView; import butterknife.OnClick; import butterknife.OnTouch; @@ -45,11 +60,20 @@ public class CreateIssueActivity extends BaseActivity labelModels = new ArrayList<>(); + @State MilestoneModel milestoneModel; + @State ArrayList users = new ArrayList<>(); private AlertDialog alertDialog; private CharSequence savedText; @@ -177,6 +201,12 @@ public static void startForResult(@NonNull Activity activity, @NonNull Intent in finish(); } + @Override public void onShowIssueMisc() { + TransitionManager.beginDelayedTransition(findViewById(R.id.parent)); + issueMiscLayout.setVisibility(getPresenter().isCollaborator() ? View.VISIBLE : View.GONE); + //TODO + } + @NonNull @Override public CreateIssuePresenter providePresenter() { return new CreateIssuePresenter(); } @@ -214,6 +244,17 @@ public static void startForResult(@NonNull Activity activity, @NonNull Intent in } } if (issue != null) { + Logger.e(issue.getLabels(), issue.getMilestone(), issue.getAssignees()); + if (issue.getLabels() != null) { + onSelectedLabels(new ArrayList<>(issue.getLabels())); + } + if (issue.getAssignees() != null) { + onSelectedAssignees(new ArrayList<>(issue.getAssignees()), false); + } + if (issue.getMilestone() != null) { + milestoneModel = issue.getMilestone(); + onMilestoneSelected(milestoneModel); + } if (!InputHelper.isEmpty(issue.getTitle())) { if (title.getEditText() != null) title.getEditText().setText(issue.getTitle()); } @@ -222,6 +263,17 @@ public static void startForResult(@NonNull Activity activity, @NonNull Intent in } } if (pullRequest != null) { + if (pullRequest.getLabels() != null) { + onSelectedLabels(new ArrayList<>(pullRequest.getLabels())); + } + if (pullRequest.getAssignees() != null) { + users.addAll(pullRequest.getAssignees()); + onSelectedAssignees(new ArrayList<>(pullRequest.getAssignees()), false); + } + if (pullRequest.getMilestone() != null) { + milestoneModel = pullRequest.getMilestone(); + onMilestoneSelected(milestoneModel); + } if (!InputHelper.isEmpty(pullRequest.getTitle())) { if (title.getEditText() != null) title.getEditText().setText(pullRequest.getTitle()); } @@ -230,6 +282,7 @@ public static void startForResult(@NonNull Activity activity, @NonNull Intent in } } } + getPresenter().checkAuthority(login, repoId); if (isFeedback || ("k0shk0sh".equalsIgnoreCase(login) && repoId.equalsIgnoreCase("FastHub"))) { setTitle(R.string.submit_feedback); getPresenter().onCheckAppVersion(); @@ -295,6 +348,68 @@ public static void startForResult(@NonNull Activity activity, @NonNull Intent in } @OnClick(R.id.submit) public void onClick() { - getPresenter().onSubmit(InputHelper.toString(title), savedText, login, repoId, issue, pullRequest); + getPresenter().onSubmit(InputHelper.toString(title), savedText, login, repoId, issue, pullRequest, labelModels, milestoneModel, users); + } + + @OnClick({R.id.addAssignee, R.id.addLabels, R.id.addMilestone}) public void onViewClicked(View view) { + switch (view.getId()) { + case R.id.addAssignee: + AssigneesDialogFragment.newInstance(login, repoId, false) + .show(getSupportFragmentManager(), "AssigneesDialogFragment"); + break; + case R.id.addLabels: + LabelListModel labelModels = new LabelListModel(); + labelModels.addAll(this.labelModels); + LabelsDialogFragment.newInstance(labelModels, repoId, login) + .show(getSupportFragmentManager(), "LabelsDialogFragment"); + break; + case R.id.addMilestone: + MilestoneDialogFragment.newInstance(login, repoId) + .show(getSupportFragmentManager(), "MilestoneDialogFragment"); + break; + } + } + + @Override public void onSelectedLabels(@NonNull ArrayList labelModels) { + this.labelModels.clear(); + this.labelModels.addAll(labelModels); + SpannableBuilder builder = SpannableBuilder.builder(); + for (int i = 0; i < labelModels.size(); i++) { + LabelModel labelModel = labelModels.get(i); + int color = Color.parseColor("#" + labelModel.getColor()); + if (i > 0) { + builder.append(" ").append(" " + labelModel.getName() + " ", new LabelSpan(color)); + } else { + builder.append(labelModel.getName() + " ", new LabelSpan(color)); + } + } + this.labels.setText(builder); + } + + @Override public void onMilestoneSelected(@NonNull MilestoneModel milestoneModel) { + Logger.e(milestoneModel.getTitle(), milestoneModel.getDescription(), milestoneModel.getNumber()); + this.milestoneModel = milestoneModel; + milestoneTitle.setText(milestoneModel.getTitle()); + if (!InputHelper.isEmpty(milestoneModel.getDescription())) { + milestoneDescription.setText(milestoneModel.getDescription()); + milestoneDescription.setVisibility(View.VISIBLE); + } else { + milestoneDescription.setText(null); + milestoneDescription.setVisibility(View.GONE); + } + } + + @Override public void onSelectedAssignees(@NonNull ArrayList users, boolean isAssignees) { + this.users.clear(); + this.users.addAll(users); + SpannableBuilder builder = SpannableBuilder.builder(); + for (int i = 0; i < users.size(); i++) { + User user = users.get(i); + builder.append(user.getLogin()); + if (i != users.size() - 1) { + builder.append(", "); + } + } + assignee.setText(builder); } } diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssueMvp.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssueMvp.java index 7fe572bec..7740da182 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssueMvp.java +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssueMvp.java @@ -4,9 +4,17 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.fastaccess.data.dao.LabelModel; +import com.fastaccess.data.dao.MilestoneModel; import com.fastaccess.data.dao.model.Issue; import com.fastaccess.data.dao.model.PullRequest; +import com.fastaccess.data.dao.model.User; import com.fastaccess.ui.base.mvp.BaseMvp; +import com.fastaccess.ui.modules.repos.extras.assignees.AssigneesMvp; +import com.fastaccess.ui.modules.repos.extras.labels.LabelsMvp; +import com.fastaccess.ui.modules.repos.extras.milestone.MilestoneMvp; + +import java.util.ArrayList; /** * Created by Kosh on 19 Feb 2017, 12:12 PM @@ -14,7 +22,8 @@ public interface CreateIssueMvp { - interface View extends BaseMvp.FAView { + interface View extends BaseMvp.FAView, LabelsMvp.SelectedLabelsListener, AssigneesMvp.SelectedAssigneesListener, + MilestoneMvp.OnMilestoneSelected { void onSetCode(@NonNull CharSequence charSequence); void onTitleError(boolean isEmptyTitle); @@ -26,14 +35,23 @@ interface View extends BaseMvp.FAView { void onSuccessSubmission(PullRequest issueModel); void onShowUpdate(); + + void onShowIssueMisc(); } interface Presenter extends BaseMvp.FAPresenter { + + void checkAuthority(@NonNull String login, @NonNull String repoId); + void onActivityForResult(int resultCode, int requestCode, Intent intent); void onSubmit(@NonNull String title, @NonNull CharSequence description, @NonNull String login, - @NonNull String repo, @Nullable Issue issueModel, @Nullable PullRequest pullRequestModel); + @NonNull String repo, @Nullable Issue issueModel, @Nullable PullRequest pullRequestModel, + @Nullable ArrayList labels, @Nullable MilestoneModel milestoneModel, + @Nullable ArrayList users); void onCheckAppVersion(); + + boolean isCollaborator(); } } diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssuePresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssuePresenter.java index 00d5989f6..a788b45d4 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssuePresenter.java +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssuePresenter.java @@ -5,24 +5,47 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.annimon.stream.Collectors; +import com.annimon.stream.Stream; import com.fastaccess.BuildConfig; import com.fastaccess.R; import com.fastaccess.data.dao.CreateIssueModel; import com.fastaccess.data.dao.IssueRequestModel; +import com.fastaccess.data.dao.LabelListModel; +import com.fastaccess.data.dao.LabelModel; +import com.fastaccess.data.dao.MilestoneModel; +import com.fastaccess.data.dao.UsersListModel; import com.fastaccess.data.dao.model.Issue; +import com.fastaccess.data.dao.model.Login; import com.fastaccess.data.dao.model.PullRequest; +import com.fastaccess.data.dao.model.User; import com.fastaccess.helper.BundleConstant; import com.fastaccess.helper.InputHelper; +import com.fastaccess.helper.RxHelper; import com.fastaccess.provider.rest.RestProvider; import com.fastaccess.ui.base.mvp.BaseMvp; import com.fastaccess.ui.base.mvp.presenter.BasePresenter; +import java.util.ArrayList; + /** * Created by Kosh on 19 Feb 2017, 12:18 PM */ public class CreateIssuePresenter extends BasePresenter implements CreateIssueMvp.Presenter { + @com.evernote.android.state.State boolean isCollaborator; + + + @Override public void checkAuthority(@NonNull String login, @NonNull String repoId) { + manageViewDisposable(RxHelper.getObservable(RestProvider.getRepoService(isEnterprise()). + isCollaborator(login, repoId, Login.getUser().getLogin())) + .subscribe(booleanResponse -> { + isCollaborator = booleanResponse.code() == 204; + sendToView(CreateIssueMvp.View::onShowIssueMisc); + }, Throwable::printStackTrace)); + } + @Override public void onActivityForResult(int resultCode, int requestCode, Intent intent) { if (resultCode == Activity.RESULT_OK && requestCode == BundleConstant.REQUEST_CODE) { if (intent != null && intent.getExtras() != null) { @@ -34,9 +57,11 @@ public class CreateIssuePresenter extends BasePresenter imp } } - @Override public void onSubmit(@NonNull String title, @NonNull CharSequence description, - @NonNull String login, @NonNull String repo, - @Nullable Issue issue, @Nullable PullRequest pullRequestModel) { + @Override public void onSubmit(@NonNull String title, @NonNull CharSequence description, @NonNull String login, + @NonNull String repo, @Nullable Issue issue, @Nullable PullRequest pullRequestModel, + @Nullable ArrayList labels, @Nullable MilestoneModel milestoneModel, + @Nullable ArrayList users) { + boolean isEmptyTitle = InputHelper.isEmpty(title); if (getView() != null) { getView().onTitleError(isEmptyTitle); @@ -46,6 +71,17 @@ public class CreateIssuePresenter extends BasePresenter imp CreateIssueModel createIssue = new CreateIssueModel(); createIssue.setBody(InputHelper.toString(description)); createIssue.setTitle(title); + if (isCollaborator) { + if (labels != null && !labels.isEmpty()) { + createIssue.setLabels(Stream.of(labels).map(LabelModel::getName).collect(Collectors.toCollection(ArrayList::new))); + } + if (users != null && !users.isEmpty()) { + createIssue.setAssignees(Stream.of(users).map(User::getLogin).collect(Collectors.toCollection(ArrayList::new))); + } + if (milestoneModel != null) { + createIssue.setMilestone(milestoneModel); + } + } makeRestCall(RestProvider.getIssueService(isEnterprise()).createIssue(login, repo, createIssue), issueModel -> { if (issueModel != null) { @@ -59,6 +95,21 @@ public class CreateIssuePresenter extends BasePresenter imp issue.setBody(InputHelper.toString(description)); issue.setTitle(title); int number = issue.getNumber(); + if (isCollaborator) { + if (labels != null) { + LabelListModel labelModels = new LabelListModel(); + labelModels.addAll(labels); + issue.setLabels(labelModels); + } + if (milestoneModel != null) { + issue.setMilestone(milestoneModel); + } + if (users != null) { + UsersListModel usersListModel = new UsersListModel(); + usersListModel.addAll(users); + issue.setAssignees(usersListModel); + } + } IssueRequestModel requestModel = IssueRequestModel.clone(issue, false); makeRestCall(RestProvider.getIssueService(isEnterprise()).editIssue(login, repo, number, requestModel), issueModel -> { @@ -73,6 +124,21 @@ public class CreateIssuePresenter extends BasePresenter imp int number = pullRequestModel.getNumber(); pullRequestModel.setBody(InputHelper.toString(description)); pullRequestModel.setTitle(title); + if (isCollaborator) { + if (labels != null) { + LabelListModel labelModels = new LabelListModel(); + labelModels.addAll(labels); + pullRequestModel.setLabels(labelModels); + } + if (milestoneModel != null) { + pullRequestModel.setMilestone(milestoneModel); + } + if (users != null) { + UsersListModel usersListModel = new UsersListModel(); + usersListModel.addAll(users); + pullRequestModel.setAssignees(usersListModel); + } + } IssueRequestModel requestModel = IssueRequestModel.clone(pullRequestModel, false); makeRestCall(RestProvider.getPullRequestService(isEnterprise()).editPullRequest(login, repo, number, requestModel) .flatMap(pullRequest1 -> RestProvider.getIssueService(isEnterprise()).getIssue(login, repo, number), @@ -91,6 +157,7 @@ public class CreateIssuePresenter extends BasePresenter imp } } } + } @Override public void onCheckAppVersion() { @@ -105,4 +172,8 @@ public class CreateIssuePresenter extends BasePresenter imp } }, false); } + + @Override public boolean isCollaborator() { + return isCollaborator; + } } diff --git a/app/src/main/res/layouts/main_layouts/layout/create_issue_layout.xml b/app/src/main/res/layouts/main_layouts/layout/create_issue_layout.xml index 21cb1d0bc..fc09b8e39 100644 --- a/app/src/main/res/layouts/main_layouts/layout/create_issue_layout.xml +++ b/app/src/main/res/layouts/main_layouts/layout/create_issue_layout.xml @@ -1,7 +1,9 @@ - + android:orientation="vertical" + android:visibility="gone" + tools:visibility="visible"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/issue_popup_layout.xml b/app/src/main/res/layouts/main_layouts/layout/issue_popup_layout.xml index 09ebef370..94eba3c58 100644 --- a/app/src/main/res/layouts/main_layouts/layout/issue_popup_layout.xml +++ b/app/src/main/res/layouts/main_layouts/layout/issue_popup_layout.xml @@ -193,8 +193,6 @@ style="@style/TextAppearance.AppCompat.Medium" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingBottom="@dimen/spacing_normal" - android:paddingTop="@dimen/spacing_normal" tools:text="Label 1"/> From b4ea0d2f5138301efce41037dec2fd72cb2547e4 Mon Sep 17 00:00:00 2001 From: k0shk0sh Date: Tue, 3 Oct 2017 11:29:57 +0200 Subject: [PATCH 27/36] made promo codes more easier to handle --- .../fastaccess/data/dao/ProUsersModel.java | 56 +++++++++++++++++++ .../main/premium/GmsTaskListeners.java | 48 ++++++++++++++++ .../modules/main/premium/PremiumPresenter.kt | 44 ++++++++++----- 3 files changed, 133 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/fastaccess/data/dao/ProUsersModel.java create mode 100644 app/src/main/java/com/fastaccess/ui/modules/main/premium/GmsTaskListeners.java diff --git a/app/src/main/java/com/fastaccess/data/dao/ProUsersModel.java b/app/src/main/java/com/fastaccess/data/dao/ProUsersModel.java new file mode 100644 index 000000000..b6be38500 --- /dev/null +++ b/app/src/main/java/com/fastaccess/data/dao/ProUsersModel.java @@ -0,0 +1,56 @@ +package com.fastaccess.data.dao; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Created by Hashemsergani on 03.10.17. + */ + +public class ProUsersModel implements Parcelable { + private int count; + private boolean allowed; + private int type; + + public int getCount() { return count;} + + public void setCount(int count) { this.count = count;} + + public boolean isAllowed() { return allowed;} + + public void setAllowed(boolean allowed) { this.allowed = allowed;} + + public int getType() { return type;} + + public void setType(int type) { this.type = type;} + + @Override public String toString() { + return "ProUsersModel{" + + ", count=" + count + + ", allowed=" + allowed + + ", type=" + type + + '}'; + } + + public ProUsersModel() {} + + @Override public int describeContents() { return 0; } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.count); + dest.writeByte(this.allowed ? (byte) 1 : (byte) 0); + dest.writeInt(this.type); + } + + protected ProUsersModel(Parcel in) { + this.count = in.readInt(); + this.allowed = in.readByte() != 0; + this.type = in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + @Override public ProUsersModel createFromParcel(Parcel source) {return new ProUsersModel(source);} + + @Override public ProUsersModel[] newArray(int size) {return new ProUsersModel[size];} + }; +} diff --git a/app/src/main/java/com/fastaccess/ui/modules/main/premium/GmsTaskListeners.java b/app/src/main/java/com/fastaccess/ui/modules/main/premium/GmsTaskListeners.java new file mode 100644 index 000000000..e15fd70c5 --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/modules/main/premium/GmsTaskListeners.java @@ -0,0 +1,48 @@ +package com.github.b3er.rxfirebase.common; + +import android.support.annotation.NonNull; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; +import io.reactivex.CompletableEmitter; +import io.reactivex.SingleEmitter; + +public final class GmsTaskListeners { + + private GmsTaskListeners() { + throw new AssertionError("No instances"); + } + + public static OnCompleteListener listener(@NonNull final SingleEmitter emitter) { + return new OnCompleteListener() { + @Override public void onComplete(@NonNull Task task) { + if (!task.isSuccessful()) { + if (!emitter.isDisposed()) { + emitter.onError(task.getException()); + } + return; + } + + if (!emitter.isDisposed()) { + emitter.onSuccess(task.getResult()); + } + } + }; + } + + public static OnCompleteListener listener(@NonNull final CompletableEmitter emitter) { + return new OnCompleteListener() { + @Override public void onComplete(@NonNull Task task) { + if (!task.isSuccessful()) { + if (!emitter.isDisposed()) { + emitter.onError(task.getException()); + } + return; + } + + if (!emitter.isDisposed()) { + emitter.onComplete(); + } + } + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumPresenter.kt b/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumPresenter.kt index ac73d355c..034deaeaf 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumPresenter.kt +++ b/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumPresenter.kt @@ -1,9 +1,12 @@ package com.fastaccess.ui.modules.main.premium +import com.fastaccess.data.dao.ProUsersModel +import com.fastaccess.helper.Logger import com.fastaccess.helper.PrefGetter import com.fastaccess.helper.RxHelper import com.fastaccess.ui.base.mvp.presenter.BasePresenter import com.github.b3er.rxfirebase.database.data +import com.github.b3er.rxfirebase.database.rxUpdateChildren import com.google.firebase.database.FirebaseDatabase import com.google.firebase.database.GenericTypeIndicator import io.reactivex.Observable @@ -15,32 +18,43 @@ import io.reactivex.Observable class PremiumPresenter : BasePresenter(), PremiumMvp.Presenter { override fun onCheckPromoCode(promo: String) { val ref = FirebaseDatabase.getInstance().reference - manageDisposable(RxHelper.getObservable(ref.child("promoCodes") + manageDisposable(RxHelper.getObservable(ref.child("fasthub_pro").child(promo) .data() .toObservable()) .doOnSubscribe { sendToView { it.showProgress(0) } } .flatMap { - var exists: Boolean? = false + var user = ProUsersModel() + Logger.e(it.exists(), it.hasChildren(), it.value) if (it.exists()) { - val gti = object : GenericTypeIndicator>() {} - val map = it.getValue(gti) - exists = map?.contains(promo) - } - return@flatMap Observable.just(exists) - } - .doOnComplete { sendToView { it.hideProgress() } } - .subscribe({ - when (it) { - true -> sendToView { - if (promo.contains("student")) { + val gti = object : GenericTypeIndicator() {} + user = it.getValue(gti) ?: ProUsersModel() + Logger.e(user) + if (user.isAllowed) { + if (user.type == 1) { PrefGetter.setProItems() + user.isAllowed = false + user.count = user.count + 1 + return@flatMap ref.child("fasthub_pro").rxUpdateChildren(hashMapOf(Pair(promo, user))) + .toObservable() + .map { true } } else { PrefGetter.setProItems() PrefGetter.setEnterpriseItem() + user.count = user.count + 1 + return@flatMap ref.child("fasthub_pro").rxUpdateChildren(hashMapOf(Pair(promo, user))) + .toObservable() + .map { true } } - it.onSuccessfullyActivated() } - else -> sendToView { it.onNoMatch() } + } + return@flatMap Observable.just(user.isAllowed) + } + .doOnComplete { sendToView { it.hideProgress() } } + .subscribe({ + if (it) { + sendToView { it.onSuccessfullyActivated() } + } else { + sendToView { it.onNoMatch() } } }, ::println)) } From 22828a8785e1d1be50b064ecb7737cf3a3fac7f6 Mon Sep 17 00:00:00 2001 From: k0shk0sh Date: Tue, 3 Oct 2017 16:04:35 +0200 Subject: [PATCH 28/36] fixed milestone --- .../main/java/com/fastaccess/data/dao/CreateIssueModel.java | 6 +++--- .../modules/repos/issues/create/CreateIssuePresenter.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/fastaccess/data/dao/CreateIssueModel.java b/app/src/main/java/com/fastaccess/data/dao/CreateIssueModel.java index 97fddc879..aee571f83 100644 --- a/app/src/main/java/com/fastaccess/data/dao/CreateIssueModel.java +++ b/app/src/main/java/com/fastaccess/data/dao/CreateIssueModel.java @@ -19,7 +19,7 @@ public class CreateIssueModel implements Parcelable { private String body; private ArrayList labels; private ArrayList assignees; - private MilestoneModel milestone; + private long milestone; @Override public int describeContents() { return 0; } @@ -28,7 +28,7 @@ public class CreateIssueModel implements Parcelable { dest.writeString(this.body); dest.writeStringList(this.labels); dest.writeStringList(this.assignees); - dest.writeParcelable(this.milestone, flags); + dest.writeLong(this.milestone); } protected CreateIssueModel(Parcel in) { @@ -36,7 +36,7 @@ protected CreateIssueModel(Parcel in) { this.body = in.readString(); this.labels = in.createStringArrayList(); this.assignees = in.createStringArrayList(); - this.milestone = in.readParcelable(MilestoneModel.class.getClassLoader()); + this.milestone = in.readLong(); } public static final Creator CREATOR = new Creator() { diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssuePresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssuePresenter.java index a788b45d4..79314186b 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssuePresenter.java +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssuePresenter.java @@ -79,7 +79,7 @@ public class CreateIssuePresenter extends BasePresenter imp createIssue.setAssignees(Stream.of(users).map(User::getLogin).collect(Collectors.toCollection(ArrayList::new))); } if (milestoneModel != null) { - createIssue.setMilestone(milestoneModel); + createIssue.setMilestone(milestoneModel.getNumber()); } } makeRestCall(RestProvider.getIssueService(isEnterprise()).createIssue(login, repo, createIssue), From 5c4bf2dce60ded169689a3e25e9784c215cb9591 Mon Sep 17 00:00:00 2001 From: k0shk0sh Date: Tue, 3 Oct 2017 16:32:50 +0200 Subject: [PATCH 29/36] this commit fixes #1067 and readying to release 4.4.1 --- app/build.gradle | 4 ++-- .../fastaccess/provider/markdown/MarkDownProvider.java | 5 +++-- .../com/fastaccess/ui/widgets/markdown/MarkDownLayout.kt | 2 +- app/src/main/res/raw/changelog.html | 8 +++++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0b373ce27..c20628ef9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,8 +29,8 @@ android { applicationId "com.fastaccess.github" minSdkVersion 21 targetSdkVersion 26 - versionCode 440 - versionName "4.4.0" + versionCode 441 + versionName "4.4.1" buildConfigString "GITHUB_CLIENT_ID", (buildProperties.secrets['github_client_id'] | buildProperties.notThere['github_client_id']).string buildConfigString "GITHUB_SECRET", (buildProperties.secrets['github_secret'] | buildProperties.notThere['github_secret']).string buildConfigString "IMGUR_CLIENT_ID", (buildProperties.secrets['imgur_client_id'] | buildProperties.notThere['imgur_client_id']).string diff --git a/app/src/main/java/com/fastaccess/provider/markdown/MarkDownProvider.java b/app/src/main/java/com/fastaccess/provider/markdown/MarkDownProvider.java index 4a7efc615..290149bbb 100644 --- a/app/src/main/java/com/fastaccess/provider/markdown/MarkDownProvider.java +++ b/app/src/main/java/com/fastaccess/provider/markdown/MarkDownProvider.java @@ -314,11 +314,12 @@ public static void insertAtCursor(@NonNull EditText editText, @NonNull String te String oriContent = editText.getText().toString(); int start = editText.getSelectionStart(); int end = editText.getSelectionEnd(); - int index = start >= 0 ? start : 0; Logger.e(start, end); - if (start >= 0 && end > 0) { + if (start >= 0 && end > 0 && start != end) { editText.setText(editText.getText().replace(start, end, text)); } else { + int index = editText.getSelectionStart() >= 0 ? editText.getSelectionStart() : 0; + Logger.e(start, end, index); StringBuilder builder = new StringBuilder(oriContent); builder.insert(index, text); editText.setText(builder.toString()); diff --git a/app/src/main/java/com/fastaccess/ui/widgets/markdown/MarkDownLayout.kt b/app/src/main/java/com/fastaccess/ui/widgets/markdown/MarkDownLayout.kt index 9074e878c..835e35794 100644 --- a/app/src/main/java/com/fastaccess/ui/widgets/markdown/MarkDownLayout.kt +++ b/app/src/main/java/com/fastaccess/ui/widgets/markdown/MarkDownLayout.kt @@ -162,7 +162,7 @@ class MarkDownLayout : LinearLayout { } } - fun getSelectedText(): String? { + private fun getSelectedText(): String? { markdownListener?.getEditText()?.let { if (!it.text.toString().isBlank()) { val selectionStart = it.selectionStart diff --git a/app/src/main/res/raw/changelog.html b/app/src/main/res/raw/changelog.html index 14695aae0..bb2d14077 100644 --- a/app/src/main/res/raw/changelog.html +++ b/app/src/main/res/raw/changelog.html @@ -8,16 +8,18 @@

    FastHub changelog

    -

    Version 4.4.0 (Org Project Columns and Cards) +

    Version 4.4.1 (Org Project Columns and Cards)

    Please report the issues in FastHub repo instead, by opening the Drawer Menu and clicking on “Report an Issue” PLEASE USE IT.

    -

    Bugs , Enhancements & new Features (4.4.0) +

    Bugs , Enhancements & new Features (4.4.1)

      +
    • (New 4.4.1) Add Labels, Assignees & Milestone when creating/editing Issue
    • +
    • (New) Improvements to handle Students PRO.
    • (New) Org Project Columns & Cards (Edit, Create & Delete)
    • (New) Displaying Labels under Issue/Pr description.
    • (Enhancement) Removal of PR review limit.
    • @@ -25,7 +27,7 @@

      Bugs , Enhancements &
    • (Enhancement) Removed Loading background.
    • (Enhancement) More markdown enhancement.
    • (Fix) Lots of bug fixes.
    • -
    • There are more stuff are not mentioned, find them out :stuck_out_tongue:
    • +
    • There are more stuff are not mentioned, find them out ;).

    What left in FastHub?

    From bc91df1470311ac3532c2fa1935bf131ffa44b8a Mon Sep 17 00:00:00 2001 From: cozyplanes Date: Wed, 4 Oct 2017 00:55:41 +0900 Subject: [PATCH 30/36] Update README.md (#1060) * Update README.md --- README.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index cd288188b..0cc324982 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Yet another **open-source** GitHub client app but unlike any other app, FastHub - Offline-mode - Markdown and code highlighting support - Notifications overview and "Mark all as read" - - Search users/orgs, repos, issues/prs & code. + - Search Users/Orgs, Repos, Issues/PRs & Code. - FastHub & GitHub Pinned Repos - Trending - Wiki @@ -115,6 +115,7 @@ Read the [**contribution guide**](.github/CONTRIBUTING.md) for more detailed inf
    Thanks for those who contributed to FastHub by adding their language +

    - Chinese (Simplified) @Devifish

    - Chinese (Traditional) @maple3142

    - German @failex234

    @@ -128,6 +129,7 @@ Read the [**contribution guide**](.github/CONTRIBUTING.md) for more detailed inf

    - Czech @hejsekvojtech

    - Spanish @alete89

    - French @ptt-homme

    +

    - Korean @Astro36

    @cozyplanes

    ## FAQ @@ -139,21 +141,33 @@ Read the [**contribution guide**](.github/CONTRIBUTING.md) for more detailed inf
    - I tried to login via Access Token & OTP but it does not work? + I tried to login via Access Token & OTP but why isn't it working?

    You can't login via Access Token & OTP all together due to the lifetime of the OTP code, you'll be required to login in every few seconds.

    - Why my Private Repo Wiki does not show up? + Why are my Private Repo and Enterprise Wiki not showing up?

    It's due to FastHub scraping GitHub Wiki page & Private Repos require session token that FastHub doesn't have.

    - I login with Enterprise account but can't interact with anything other than my Enterprise GitHub -

    Well, logically, you can't access anything else other than your Enterprise, but FastHub made that possible but can't do much about it, - in most cases since your login credential doesn't exists in GitHub server. But in few - cases your GitHub account Oauth token will do the trick.

    + I login with Enterprise account but can't interact with anything other than my Enterprise GitHub. +

    Well, logically, you can't access anything else other than your Enterprise, but FastHub made that possible but can't do much about it, in most cases since your login credential doesn't exists in GitHub server. But in few cases your GitHub account Oauth token will do the trick.

    +
    + +
    + Why am I having problems editing Issues/PRs? +

    If you are unable to edit an issue in a public organization, please contact your Organization Admin to grant access to FastHub. Alternatively you can login using an Access Token with the correct permissions granted.

    +
    +
    + I'm having this issue! / I want this and that! +

    Head to https://github.com/k0shk0sh/FastHub/issues/new and create new issue for bugs or feature requests. I really encourage you to search before opening a ticket. Any duplicate request will result in it being closed immediately.

    +
    + +
    + How do I get PROMO CODE? +

    Please refer to the in-app FAQ for details.

    From b7090e5eb3176e5433a1eb77049d619c0f93dbc7 Mon Sep 17 00:00:00 2001 From: Apply55gx Date: Wed, 4 Oct 2017 08:42:54 +0200 Subject: [PATCH 31/36] Added Fabric and Kotlin Link --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0cc324982..2a0faf832 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ _Ads currently not available._ ## Specs / Open-source libraries: - Minimum **SDK 21**, _but AppCompat is used all the way ;-)_ -- **Kotlin** all new modules starting from 2.5.3 will be written in **#Kotlin**. +- [**Kotlin**](https://github.com/JetBrains/kotlin) all new modules starting from 2.5.3 will be written in **#Kotlin**. - **MVP**-architecture: [**ThirtyInch**](https://github.com/grandcentrix/ThirtyInch) because its ThirtyInch. - [**RxJava2**](https://github.com/ReactiveX/RxJava) & [**RxAndroid**](https://github.com/ReactiveX/RxAndroid) for Retrofit & background threads - [**Retrofit**](https://github.com/square/retrofit) for constructing the REST API @@ -102,7 +102,7 @@ _Ads currently not available._ - [**Toasty**](https://github.com/GrenderG/Toasty) for displaying error/success messages - [**ShapedImageView**](https://github.com/gavinliu/ShapedImageView) for round avatars - [**Material-About-Library**](https://github.com/daniel-stoneuk/material-about-library) for the about screen -- **Fabric** analytics & crash reporting. +- [**Fabric**](https://fabric.io/kits/ios/crashlytics) analytics & crash reporting. - **Android Support Libraries**, the almighty ;-) ## Contribution From 559a2b813c40350aa7fd86c53081feaea4347d77 Mon Sep 17 00:00:00 2001 From: Apply55gx Date: Wed, 4 Oct 2017 08:46:23 +0200 Subject: [PATCH 32/36] Fixed fabric link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a0faf832..a03f716f2 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ _Ads currently not available._ - [**Toasty**](https://github.com/GrenderG/Toasty) for displaying error/success messages - [**ShapedImageView**](https://github.com/gavinliu/ShapedImageView) for round avatars - [**Material-About-Library**](https://github.com/daniel-stoneuk/material-about-library) for the about screen -- [**Fabric**](https://fabric.io/kits/ios/crashlytics) analytics & crash reporting. +- [**Fabric**](https://fabric.io/kits/android/crashlytics) analytics & crash reporting. - **Android Support Libraries**, the almighty ;-) ## Contribution From 0bf000df38bfba5eb0e48562f23ae756840e219d Mon Sep 17 00:00:00 2001 From: cozyplanes Date: Wed, 4 Oct 2017 22:59:17 +0900 Subject: [PATCH 33/36] Update README.md (Newline Issue Again, Header Size Inconsistency) (#1078) * Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0cc324982..57876c35f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Yet another **open-source** GitHub client app but unlike any other app, FastHub alt="Direct apk download" height="80">](https://github.com/k0shk0sh/FastHub/releases/latest) -# Features +## Features - **App** - Three login types (Basic Auth), (Access Token) or via (OAuth) - Multiple Accounts @@ -129,7 +129,7 @@ Read the [**contribution guide**](.github/CONTRIBUTING.md) for more detailed inf

    - Czech @hejsekvojtech

    - Spanish @alete89

    - French @ptt-homme

    -

    - Korean @Astro36

    @cozyplanes

    +

    - Korean @Astro36 @cozyplanes

    ## FAQ From 8f131ca43518361ac917ee8f24d59f11ac77a0ec Mon Sep 17 00:00:00 2001 From: Stargamers Date: Thu, 5 Oct 2017 17:02:43 +0200 Subject: [PATCH 34/36] Updated German translation again --- app/src/main/res/values-de/strings.xml | 56 ++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 6dacd3b78..97e8742cf 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -399,6 +399,62 @@ Benachrichtigungs Ton auswählen Deaktiviere automatische wiedergabe von GIFs Deaktiviere GIF wiedergabe + angeforderte Änderungen + Google Play-Dienste sind nicht verfügbar + GIST bearbeiten + Inhalt + erweitern + SHA-1 Kopieren + Als Code anzeigen + In-App-Animationen deaktivieren + Deaktiviere in-App-Animationen überall. + Dieser PR kann jetzt\jetzt nicht zusammengeführt werden. + Projekte + Keine Projekte + Keine Karten + Hinzugefügt von %s %s + Zu viele Unterschiede zur Anzeige. Bitte, sehe Sie Sie dir über den Browser an + Projekt + BITTE lESEN! + + • Warum kann ich meine Organisationen entweder private oder öffentliche sehen\nicht sehen? +

    Öffne https://github.com/Settings/Applications und suche nach FastHub, öffne es dann Blätter zu Organisation Zugang und klicke auf den Grant Knopf, + Alternativ melde dich über einen Zugang Token an welches das Setup vereinfacht.

    + +
    • Ich habe versuchtt mich mit einem Zugangs Token anzumelden amp; OTP aber es funtioniert nicht?
    +

    Du kannst dich nicht mit einem zugangs token anmelden & OTP alle Zusammen wegen der Lebenszeit des OTP codes, du musst dich alle paar sekunden anmelden

    + +
    • Warum werden mein privates Repo & Enterprise Wiki nicht angezeigt?
    +

    Es wie FastHub die GitHub Wiki page holt & Private Repos benötigen einen Sitzungs token denn FastHub nicht bekommen kann.

    + +
    • Ich habe mich mit meinen Enterprise account angemeldet kann\kann nicht mit anderen sachen interagieren außer meinem Enterprise GitHub?
    +

    Nun, logisch, kannst du nicht auf etwas anderes als Ihr Unternehmen zugreifen. FastHub versucht, so viel möglich zu erlauben, aber kann + in den meisten Fällen nicht viel dafür tun, da Ihre Anmeldedaten auf den GitHub Server nicht vorhanden sind. Aber in einem paar + fällen Ihr GitHub Konto OAuth Token wird den Trick tun.

    + +
    • Warum habe ich Probleme bei der Bearbeitung von Problemen/PRS?
    +

    Wenn du ein öffentliches organisations-repo bearbeiten willst, wende dich bitte an deine organisation, um Zugang zu FastHub zu gewähren oder Zugangsdaten zu verwenden!.

    + +
    • Ich habe das problem oder Ich möchte dies & das!!
    +

    Gehe zu https://github.com/k0shk0sh/FastHub/issues/new und erstelle ein neues issue für bugs oder Feature Requests, Ich möchte dich ermutigen + das du vor dem erstellen eines Tickets suchst ob es das schon existiert.Jeder doppelter Request wird dazu führen dass du sofort ausgeschlossen wirst.

    + +
    • Wie bekomme ich einen PROMO CODE?
    +

    Wenn du ein Student bist musst du mir eine e-Mail schicken,in der du mir beweist das du ein Student bist, Du brauchst die unten stehen den dokumente:

    +
      +
    • Deine Universitätenausweis & deinen Personalausweis (der deinen Namen zeigt & und dein Gesicht um es zu vergleichen!)
    • +
    • Dein Uni-Start & Enddatum
    • +
    • Bewerte FastHub im Play Store
    • +
    +

    Wenn du kein Student bist und du kannst es dir nicht leisten für PRO zu bezahlen, musst du:

    +
      +
    • Einen Arctikel über Fasthub schreiben in Social Media wie (Medium)
    • +
    • Bewerte FastHub im Play Store
    • +
    + ]]>
    + FAQ + Kommentare erfolgreich hinzugefügt From df73a96611d4a5a09a4644493caa68d85569d73b Mon Sep 17 00:00:00 2001 From: k0shk0sh Date: Fri, 6 Oct 2017 21:17:54 +0200 Subject: [PATCH 35/36] this commit fixes #1088 fixes #1085 fixes #1081 fixes #1073 fixes #1028 --- app/build.gradle | 2 +- app/src/main/assets/md/github_dark.css | 2 +- .../timeline/handler/QouteHandler.java | 5 +- .../timeline/handler/TableHandler.java | 106 ++++++------------ .../popup/EditorLinkImageDialogFragment.java | 2 +- .../ui/modules/gists/GistsListActivity.java | 11 ++ .../ui/modules/gists/gist/GistActivity.java | 5 + .../modules/main/premium/PremiumActivity.kt | 2 +- .../modules/main/premium/PremiumPresenter.kt | 13 +-- .../issues/create/CreateIssuePresenter.java | 2 +- .../layout/emoji_popup_layout.xml | 3 +- build.gradle | 2 +- 12 files changed, 62 insertions(+), 93 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c20628ef9..f940a283f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,7 +24,7 @@ android { } } compileSdkVersion 26 - buildToolsVersion "26.0.1" + buildToolsVersion '26.0.2' defaultConfig { applicationId "com.fastaccess.github" minSdkVersion 21 diff --git a/app/src/main/assets/md/github_dark.css b/app/src/main/assets/md/github_dark.css index c0d70c222..a695448c7 100644 --- a/app/src/main/assets/md/github_dark.css +++ b/app/src/main/assets/md/github_dark.css @@ -82,7 +82,7 @@ body kbd { padding: 3px 5px; font-size: 11px; line-height: 10px; - color: #656d78; + color: #fff; vertical-align: middle; border: solid 1px #656d78; border-bottom-color: #bbb; diff --git a/app/src/main/java/com/fastaccess/provider/timeline/handler/QouteHandler.java b/app/src/main/java/com/fastaccess/provider/timeline/handler/QouteHandler.java index 2b098cec0..e26b26774 100644 --- a/app/src/main/java/com/fastaccess/provider/timeline/handler/QouteHandler.java +++ b/app/src/main/java/com/fastaccess/provider/timeline/handler/QouteHandler.java @@ -21,10 +21,7 @@ public void handleTagNode(TagNode node, SpannableStringBuilder builder, int start, int end) { builder.append("\n"); - builder.setSpan(new MarkDownQuoteSpan(color), start + 1, builder.length() - 1, 33); + builder.setSpan(new MarkDownQuoteSpan(color), (start > builder.length() - 1) ? start + 1 : start, builder.length() - 1, 33); builder.append("\n"); } - - - } diff --git a/app/src/main/java/com/fastaccess/provider/timeline/handler/TableHandler.java b/app/src/main/java/com/fastaccess/provider/timeline/handler/TableHandler.java index 7aac34c2a..141946f4e 100644 --- a/app/src/main/java/com/fastaccess/provider/timeline/handler/TableHandler.java +++ b/app/src/main/java/com/fastaccess/provider/timeline/handler/TableHandler.java @@ -19,7 +19,6 @@ import android.text.style.ImageSpan; import net.nightwhistler.htmlspanner.TagNodeHandler; -import net.nightwhistler.htmlspanner.spans.CenterSpan; import org.htmlcleaner.TagNode; @@ -38,55 +37,40 @@ public class TableHandler extends TagNodeHandler { private int tableWidth = 500; private Typeface typeFace = Typeface.DEFAULT; - private float textSize = 30f; + private float textSize = 28f; private int textColor = Color.BLACK; - private static final int PADDING = 20; - /** - * Sets how wide the table should be. - * - * @param tableWidth - */ - public void setTableWidth(int tableWidth) { - this.tableWidth = tableWidth; + @Override public boolean rendersContent() { + return true; } - /** - * Sets the text colour to use. - *

    - * Default is black. - * - * @param textColor - */ - public void setTextColor(int textColor) { - this.textColor = textColor; - } + @Override public void handleTagNode(TagNode node, SpannableStringBuilder builder, int start, int end) { + Table table = getTable(node); + for (int i = 0; i < table.getRows().size(); i++) { + List row = table.getRows().get(i); + builder.append("\uFFFC"); + TableRowDrawable drawable = new TableRowDrawable(row, table.isDrawBorder()); + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight()); + builder.setSpan(new ImageSpan(drawable), start + i, builder.length(), 33); - /** - * Sets the font size to use. - *

    - * Default is 16f. - * - * @param textSize - */ - public void setTextSize(float textSize) { - this.textSize = textSize; + } + builder.append("\uFFFC"); + Drawable drawable = new TableRowDrawable(new ArrayList(), table.isDrawBorder()); + drawable.setBounds(0, 0, tableWidth, 1); + builder.setSpan(new ImageSpan(drawable), builder.length() - 1, builder.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + builder.setSpan((AlignmentSpan) () -> Alignment.ALIGN_CENTER, start, builder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + builder.append("\n"); } - /** - * Sets the TypeFace to use. - *

    - * Default is Typeface.DEFAULT - * - * @param typeFace - */ - public void setTypeFace(Typeface typeFace) { - this.typeFace = typeFace; + public void setTableWidth(int tableWidth) { + this.tableWidth = tableWidth; } - @Override public boolean rendersContent() { - return true; + public void setTextColor(int textColor) { + this.textColor = textColor; } private void readNode(Object node, Table table) { @@ -143,45 +127,19 @@ private int calculateRowHeight(List row) { int rowHeight = 0; - if (columnWidth > 0) { - for (Spanned cell : row) { - StaticLayout layout = new StaticLayout(cell, textPaint, columnWidth - - 2 * PADDING, Alignment.ALIGN_NORMAL, 1f, 0f, true); - if (layout.getHeight() > rowHeight) { - rowHeight = layout.getHeight(); - } + for (Spanned cell : row) { + + StaticLayout layout = new StaticLayout(cell, textPaint, columnWidth + - 2 * PADDING, Alignment.ALIGN_NORMAL, 1.5f, 0.5f, true); + + if (layout.getHeight() > rowHeight) { + rowHeight = layout.getHeight(); } } return rowHeight; } - @Override public void handleTagNode(TagNode node, SpannableStringBuilder builder, int start, int end) { - builder.append("\n"); - Table table = getTable(node); - for (int i = 0; i < table.getRows().size(); i++) { - List row = table.getRows().get(i); - builder.append("\uFFFC"); - TableRowDrawable drawable = new TableRowDrawable(row, table.isDrawBorder()); - drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), - drawable.getIntrinsicHeight()); - builder.setSpan(new ImageSpan(drawable), start + i, builder.length(), 33); - } - builder.append("\uFFFC"); - Drawable drawable = new TableRowDrawable(new ArrayList<>(), table.isDrawBorder()); - drawable.setBounds(0, 0, tableWidth, 1); - builder.setSpan(new ImageSpan(drawable), builder.length() - 1, builder.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - builder.setSpan((AlignmentSpan) () -> Alignment.ALIGN_CENTER, start, builder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - builder.setSpan(new CenterSpan(), start, builder.length(), 33); - builder.append("\n"); - } - - /** - * Drawable of the table, which does the actual rendering. - * - * @author Alex Kuiper. - */ private class TableRowDrawable extends Drawable { private List tableRow; @@ -222,7 +180,7 @@ private class TableRowDrawable extends Drawable { StaticLayout layout = new StaticLayout(tableRow.get(i), getTextPaint(), (columnWidth - 2 * PADDING), - Alignment.ALIGN_NORMAL, 1f, 0f, true); + Alignment.ALIGN_NORMAL, 1.5f, 0.5f, true); canvas.translate(offset + PADDING, 0); layout.draw(canvas); diff --git a/app/src/main/java/com/fastaccess/ui/modules/editor/popup/EditorLinkImageDialogFragment.java b/app/src/main/java/com/fastaccess/ui/modules/editor/popup/EditorLinkImageDialogFragment.java index 8dba5f1d9..a79a2b803 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/editor/popup/EditorLinkImageDialogFragment.java +++ b/app/src/main/java/com/fastaccess/ui/modules/editor/popup/EditorLinkImageDialogFragment.java @@ -77,7 +77,7 @@ public static EditorLinkImageDialogFragment newInstance(boolean isLink, @Nullabl @Override protected void onFragmentCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { select.setVisibility(isLink() ? View.GONE : View.VISIBLE); if (savedInstanceState == null) { - link.getEditText().setText(getArguments().getString(BundleConstant.ITEM)); + title.getEditText().setText(getArguments().getString(BundleConstant.ITEM)); } } diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/GistsListActivity.java b/app/src/main/java/com/fastaccess/ui/modules/gists/GistsListActivity.java index 6d511c8b1..c84edd429 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/gists/GistsListActivity.java +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/GistsListActivity.java @@ -7,6 +7,7 @@ import android.support.design.widget.FloatingActionButton; import android.support.design.widget.TabLayout; import android.support.v4.app.Fragment; +import android.view.MenuItem; import com.fastaccess.R; import com.fastaccess.data.dao.FragmentPagerAdapterModel; @@ -17,6 +18,7 @@ import com.fastaccess.ui.base.BaseFragment; import com.fastaccess.ui.base.mvp.presenter.BasePresenter; import com.fastaccess.ui.modules.gists.create.CreateGistActivity; +import com.fastaccess.ui.modules.main.MainActivity; import com.fastaccess.ui.widgets.ViewPagerView; import net.grandcentrix.thirtyinch.TiPresenter; @@ -93,6 +95,15 @@ public static void startActivity(@NonNull Context context) { } } + @Override public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + startActivity(new Intent(this, MainActivity.class)); + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + private void setupTabs() { pager.setAdapter(new FragmentsPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapterModel.buildForGists(this))); tabs.setupWithViewPager(pager); diff --git a/app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistActivity.java b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistActivity.java index a0223b5d0..130dfe4e9 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistActivity.java +++ b/app/src/main/java/com/fastaccess/ui/modules/gists/gist/GistActivity.java @@ -31,6 +31,7 @@ import com.fastaccess.ui.base.BaseActivity; import com.fastaccess.ui.base.BaseFragment; import com.fastaccess.ui.modules.editor.comment.CommentEditorFragment; +import com.fastaccess.ui.modules.gists.GistsListActivity; import com.fastaccess.ui.modules.gists.create.CreateGistActivity; import com.fastaccess.ui.modules.gists.gist.comments.GistCommentsFragment; import com.fastaccess.ui.modules.main.premium.PremiumActivity; @@ -164,6 +165,10 @@ public static Intent createIntent(@NonNull Context context, @NonNull String gist .put(BundleConstant.EXTRA, true).end()) .show(getSupportFragmentManager(), MessageDialogView.TAG); return true; + } else if (item.getItemId() == android.R.id.home) { + GistsListActivity.startActivity(this); + finish(); + return true; } return super.onOptionsItemSelected(item); } diff --git a/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumActivity.kt b/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumActivity.kt index 9801b3141..8150393ed 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumActivity.kt +++ b/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumActivity.kt @@ -92,10 +92,10 @@ class PremiumActivity : BaseActivity(), Premi override fun onSuccessfullyActivated() { hideProgress() successActivationHolder.visibility = View.VISIBLE + FabricProvider.logPurchase(InputHelper.toString(editText)) successActivationView.addAnimatorListener(object : Animator.AnimatorListener { override fun onAnimationRepeat(p0: Animator?) {} override fun onAnimationEnd(p0: Animator?) { - FabricProvider.logPurchase(InputHelper.toString(editText)) showMessage(R.string.success, R.string.success) successResult() } diff --git a/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumPresenter.kt b/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumPresenter.kt index 034deaeaf..418ca69a8 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumPresenter.kt +++ b/app/src/main/java/com/fastaccess/ui/modules/main/premium/PremiumPresenter.kt @@ -1,7 +1,6 @@ package com.fastaccess.ui.modules.main.premium import com.fastaccess.data.dao.ProUsersModel -import com.fastaccess.helper.Logger import com.fastaccess.helper.PrefGetter import com.fastaccess.helper.RxHelper import com.fastaccess.ui.base.mvp.presenter.BasePresenter @@ -24,25 +23,23 @@ class PremiumPresenter : BasePresenter(), PremiumMvp.Presenter .doOnSubscribe { sendToView { it.showProgress(0) } } .flatMap { var user = ProUsersModel() - Logger.e(it.exists(), it.hasChildren(), it.value) - if (it.exists()) { + if (it.exists() && it.hasChildren()) { val gti = object : GenericTypeIndicator() {} user = it.getValue(gti) ?: ProUsersModel() - Logger.e(user) if (user.isAllowed) { if (user.type == 1) { PrefGetter.setProItems() user.isAllowed = false user.count = user.count + 1 - return@flatMap ref.child("fasthub_pro").rxUpdateChildren(hashMapOf(Pair(promo, user))) - .toObservable() + return@flatMap RxHelper.getObservable(ref.child("fasthub_pro").rxUpdateChildren(hashMapOf(Pair(promo, user))) + .toObservable()) .map { true } } else { PrefGetter.setProItems() PrefGetter.setEnterpriseItem() user.count = user.count + 1 - return@flatMap ref.child("fasthub_pro").rxUpdateChildren(hashMapOf(Pair(promo, user))) - .toObservable() + return@flatMap RxHelper.getObservable(ref.child("fasthub_pro").rxUpdateChildren(hashMapOf(Pair(promo, user))) + .toObservable()) .map { true } } } diff --git a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssuePresenter.java b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssuePresenter.java index 79314186b..4452103f6 100644 --- a/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssuePresenter.java +++ b/app/src/main/java/com/fastaccess/ui/modules/repos/issues/create/CreateIssuePresenter.java @@ -79,7 +79,7 @@ public class CreateIssuePresenter extends BasePresenter imp createIssue.setAssignees(Stream.of(users).map(User::getLogin).collect(Collectors.toCollection(ArrayList::new))); } if (milestoneModel != null) { - createIssue.setMilestone(milestoneModel.getNumber()); + createIssue.setMilestone((long) milestoneModel.getNumber()); } } makeRestCall(RestProvider.getIssueService(isEnterprise()).createIssue(login, repo, createIssue), diff --git a/app/src/main/res/layouts/main_layouts/layout/emoji_popup_layout.xml b/app/src/main/res/layouts/main_layouts/layout/emoji_popup_layout.xml index 5a84280b2..417978f3a 100644 --- a/app/src/main/res/layouts/main_layouts/layout/emoji_popup_layout.xml +++ b/app/src/main/res/layouts/main_layouts/layout/emoji_popup_layout.xml @@ -51,7 +51,8 @@ android:background="@color/transparent" android:hint="@string/search" android:paddingEnd="@dimen/spacing_xs_large" - android:paddingStart="@dimen/spacing_xs_large"/> + android:paddingStart="@dimen/spacing_xs_large" + android:singleLine="true"/> diff --git a/build.gradle b/build.gradle index 831bd8c31..e6fa10eb4 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-beta6' + classpath 'com.android.tools.build:gradle:3.0.0-beta7' classpath 'com.google.gms:google-services:3.0.0' classpath 'com.novoda:gradle-build-properties-plugin:0.3' classpath 'com.dicedmelon.gradle:jacoco-android:0.1.2' From 522fa585252345851dbbd02a1eee1328dd713852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Segovia=20C=C3=B3rdoba?= Date: Sun, 8 Oct 2017 23:17:26 +0200 Subject: [PATCH 36/36] Update strings.xml (#1098) Close = cerrar Clone = clonar Close issue = Cerrar issue (issue really is "problema", but the original translator used the English term) --- app/src/main/res/values-es/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ed9932dda..570fabc34 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -39,7 +39,7 @@ Colaboradores Colaboraciones por - Clonar Issue + Cerrar Issue Reabrir Issue Reabrir Cerrar