@Override
    public View getView(int position, View convertView, ViewGroup parent) {
      if (convertView == null) {
        convertView =
            LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_toc_entry, parent, false);
      }
      Section section = (Section) getItem(position);
      TextView sectionHeading = (TextView) convertView.findViewById(R.id.page_toc_item_text);
      View sectionFiller = convertView.findViewById(R.id.page_toc_filler);

      LinearLayout.LayoutParams indentLayoutParameters =
          new LinearLayout.LayoutParams(sectionFiller.getLayoutParams());
      indentLayoutParameters.width =
          (section.getLevel() - 1)
              * (int) (INDENTATION_WIDTH_DP * WikipediaApp.getInstance().getScreenDensity());
      sectionFiller.setLayoutParams(indentLayoutParameters);

      sectionHeading.setText(Html.fromHtml(section.getHeading()));

      if (section.getLevel() > 1) {
        sectionHeading.setTextColor(
            WikipediaApp.getInstance()
                .getResources()
                .getColor(getThemedAttributeId(parentActivity, R.attr.toc_subsection_text_color)));
      } else {
        sectionHeading.setTextColor(
            WikipediaApp.getInstance()
                .getResources()
                .getColor(getThemedAttributeId(parentActivity, R.attr.toc_section_text_color)));
      }
      return convertView;
    }
 public void logDone() {
   log(
       "pageID", pageId,
       "pageHeight", (int) (pageHeight / app.getScreenDensity()),
       "scrollFluxDown", (int) (scrollFluxDown / app.getScreenDensity()),
       "scrollFluxUp", (int) (scrollFluxUp / app.getScreenDensity()),
       "maxPercentViewed", getMaxPercentViewed());
 }
  private void displayLeadSection() {
    try {
      final Page page = model.getPage();
      final PageProperties pageProperties = page.getPageProperties();

      JSONObject marginPayload = new JSONObject();
      int margin =
          DimenUtil.roundedPxToDp(activity.getResources().getDimension(R.dimen.content_margin));
      marginPayload.put("marginLeft", margin);
      marginPayload.put("marginRight", margin);
      bridge.sendMessage("setMargins", marginPayload);

      JSONObject leadSectionPayload = new JSONObject();
      leadSectionPayload.put("sequence", pageSequenceNum);
      leadSectionPayload.put("title", page.getDisplayTitle());
      leadSectionPayload.put("section", page.getSections().get(0).toJSON());
      leadSectionPayload.put(
          "string_page_similar_titles", activity.getString(R.string.page_similar_titles));
      leadSectionPayload.put("string_page_issues", activity.getString(R.string.button_page_issues));
      leadSectionPayload.put("string_table_infobox", activity.getString(R.string.table_infobox));
      leadSectionPayload.put("string_table_other", activity.getString(R.string.table_other));
      leadSectionPayload.put("string_table_close", activity.getString(R.string.table_close));
      leadSectionPayload.put("string_expand_refs", activity.getString(R.string.expand_refs));
      leadSectionPayload.put("isBeta", app.getReleaseType() != WikipediaApp.RELEASE_PROD);
      leadSectionPayload.put("siteLanguage", model.getTitle().getSite().getLanguageCode());
      leadSectionPayload.put("isMainPage", page.isMainPage());
      leadSectionPayload.put("apiLevel", Build.VERSION.SDK_INT);
      bridge.sendMessage("displayLeadSection", leadSectionPayload);
      Log.d(TAG, "Sent message 'displayLeadSection' for page: " + page.getDisplayTitle());

      Utils.setupDirectionality(
          model.getTitle().getSite().getLanguageCode(), Locale.getDefault().getLanguage(), bridge);

      // Hide edit pencils if anon editing is disabled by remote killswitch or if this is a file
      // page
      JSONObject miscPayload = new JSONObject();
      boolean isAnonEditingDisabled =
          app.getRemoteConfig().getConfig().optBoolean("disableAnonEditing", false)
              && !app.getUserInfoStorage().isLoggedIn();
      miscPayload.put("noedit", (isAnonEditingDisabled || page.isFilePage() || page.isMainPage()));
      miscPayload.put("protect", !pageProperties.canEdit());
      bridge.sendMessage("setPageProtected", miscPayload);
    } catch (JSONException e) {
      // This should never happen
      throw new RuntimeException(e);
    }

    if (webView.getVisibility() != View.VISIBLE) {
      webView.setVisibility(View.VISIBLE);
    }

    refreshView.setRefreshing(false);
    activity.updateProgressBar(true, true, 0);
  }
 /**
  * Called when an exception is thrown in the background process. Checks whether the exception is
  * an SSLException and, if so, prompts user to try again if the SSLException is the first.
  *
  * <p>Called on the UI Thread.
  *
  * <p>Default implementation just throws it as a RuntimeException, so exceptions are never
  * swallowed. Unless specific exceptions are handled.
  *
  * @param caught The exception that was thrown.
  */
 @Override
 public void onCatch(Throwable caught) {
   if (Utils.throwableContainsSpecificType(caught, SSLException.class)
       && WikipediaApp.getInstance().incSslFailCount() < 2) {
     WikipediaApp.getInstance().setSslFallback(true);
     if (!isCancelled()) {
       NetworkUtils.toastNetworkFail();
     }
     cancel();
     return;
   }
   throw new RuntimeException(caught);
 }
  private void refreshOneSavedPage(@NonNull final PageTitle title) {
    getApiService(title)
        .pageCombo(
            title.getPrefixedText(),
            !WikipediaApp.getInstance().isImageDownloadEnabled(),
            new SaveOtherPageCallback(title) {
              @Override
              protected void onComplete() {
                if (!progressDialog.isShowing()) {
                  isRefreshCancelled = true;
                  // no longer attached to activity!
                  return;
                }
                savedPagesCompleted++;
                progressDialog.setProgress(savedPagesCompleted);
                L.d("Count is " + savedPagesCompleted + " of " + savedPages.size());
                if (savedPagesCompleted == savedPages.size()) {
                  progressDialog.dismiss();
                }
              }

              @Override
              protected void onError() {
                isRefreshCancelled = true;
                if (!progressDialog.isShowing()) {
                  // no longer attached to activity!
                  return;
                }
                progressDialog.dismiss();
                getErrorDialog().show();
              }
            });
  }
public abstract class ApiTask<T> extends SaneAsyncTask<T> {
  private static final boolean VERBOSE = WikipediaApp.getInstance().isDevRelease();
  private final Api api;

  public ApiTask(int concurrency, Api api) {
    super(concurrency);
    this.api = api;
  }

  @Override
  public T performTask() throws Throwable {
    RequestBuilder request = buildRequest(api);
    if (VERBOSE) {
      Log.v("ApiTask", buildUrl(api.getApiUrl().toString(), request.getParams()));
    }
    ApiResult result = makeRequest(request);
    return processResult(result);
  }

  /**
   * Called when an exception is thrown in the background process. Checks whether the exception is
   * an SSLException and, if so, prompts user to try again if the SSLException is the first.
   *
   * <p>Called on the UI Thread.
   *
   * <p>Default implementation just throws it as a RuntimeException, so exceptions are never
   * swallowed. Unless specific exceptions are handled.
   *
   * @param caught The exception that was thrown.
   */
  @Override
  public void onCatch(Throwable caught) {
    if (Utils.throwableContainsSpecificType(caught, SSLException.class)
        && WikipediaApp.getInstance().incSslFailCount() < 2) {
      WikipediaApp.getInstance().setSslFallback(true);
      if (!isCancelled()) {
        NetworkUtils.toastNetworkFail();
      }
      cancel();
      return;
    }
    throw new RuntimeException(caught);
  }

  protected ApiResult makeRequest(RequestBuilder builder) throws ApiException {
    return builder.get();
  }

  public abstract RequestBuilder buildRequest(Api api);

  public abstract T processResult(ApiResult result) throws Throwable;

  private String buildUrl(String url, Map<String, String> params) {
    Uri.Builder builder = new Uri.Builder().encodedPath(url);
    for (Map.Entry<String, String> param : params.entrySet()) {
      builder.appendQueryParameter(param.getKey(), param.getValue());
    }
    return builder.build().toString();
  }
}
  private void setState(int state, int subState) {
    if (!fragment.isAdded()) {
      return;
    }
    this.state = state;
    this.subState = subState;
    activity.supportInvalidateOptionsMenu();

    // FIXME: Move this out into a PageComplete event of sorts
    if (state == STATE_COMPLETE_FETCH) {
      fragment.setupToC(model, isFirstPage());

      // add the page to cache!
      if (cacheOnComplete) {
        app.getPageCache()
            .put(
                model.getTitleOriginal(),
                model.getPage(),
                new PageCache.CachePutListener() {
                  @Override
                  public void onPutComplete() {}

                  @Override
                  public void onPutError(Throwable e) {
                    Log.e(TAG, "Failed to add page to cache.", e);
                  }
                });
      }
    }
  }
  @Override
  public void onCreate() {
    super.onCreate();
    ACRA.init(this);

    bus = new Bus();

    final Resources resources = getResources();
    ViewAnimations.init(resources);
    screenDensity = resources.getDisplayMetrics().density;

    PrefKeys.initPreferenceKeys(resources);

    // Enable debugging on the webview
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      WebView.setWebContentsDebuggingEnabled(true);
    }

    Api.setConnectionFactory(new OkHttpConnectionFactory(this));

    if (WikipediaApp.isWikipediaZeroDevmodeOn()) {
      IntentFilter connFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
      connChangeReceiver = new ConnectionChangeReceiver();
      this.registerReceiver(connChangeReceiver, connFilter);
    }

    try {
      appVersionString = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
    } catch (PackageManager.NameNotFoundException e) {
      // This will never happen!
      throw new RuntimeException(e);
    }

    new PerformMigrationsTask().execute();
  }
 public PageScrollFunnel(WikipediaApp app, int pageId) {
   super(
       app,
       SCHEMA_NAME,
       REV_ID,
       app.isProdRelease() ? Funnel.SAMPLE_LOG_100 : Funnel.SAMPLE_LOG_ALL);
   this.app = app;
   this.pageId = pageId;
 }
  public void setupToC(final Page page, Site site, boolean firstPage) {
    tocProgress.setVisibility(View.GONE);
    tocList.setVisibility(View.VISIBLE);

    headerView.setText(Html.fromHtml(page.getDisplayTitle()));
    headerView.setOnClickListener(
        new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            scrollToSection(page.getSections().get(0));
            wasClicked = true;
            funnel.logClick(0, page.getTitle().getDisplayText());
            hide();
          }
        });

    tocList.setAdapter(new ToCAdapter(page), site.getLanguageCode());
    tocList.setOnItemClickListener(
        new AdapterView.OnItemClickListener() {
          @Override
          public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            Section section = (Section) parent.getAdapter().getItem(position);
            scrollToSection(section);
            wasClicked = true;
            funnel.logClick(position, section.getHeading());
            hide();
          }
        });

    funnel =
        new ToCInteractionFunnel(
            WikipediaApp.getInstance(),
            site,
            page.getPageProperties().getPageId(),
            tocList.getAdapter().getCount());

    if (!page.isMainPage() && !firstPage) {
      if (WikipediaApp.getInstance().getOnboardingStateMachine().isTocTutorialEnabled()) {
        showTocOnboarding();
      }
    }
  }
@RunWith(AndroidJUnit4.class)
public class LoginTaskTest {
  private static final Site TEST_WIKI_SITE = new Site("test.wikipedia.org");
  private static final String SUCCESS = "Success";
  private static final String USERNAME = getString(R.string.test_username);
  private static final String PASSWORD = getString(R.string.test_password);

  private final WikipediaApp app = WikipediaApp.getInstance();
  private final TestLatch completionLatch = new TestLatch();

  @Test
  public void testLogin() throws Throwable {
    runOnMainSync(
        new Runnable() {
          @Override
          public void run() {
            loginTestTask.execute();
          }
        });
    completionLatch.await();
  }

  private LoginTask loginTestTask =
      new LoginTask(app, TEST_WIKI_SITE, USERNAME, PASSWORD) {
        @Override
        public void onFinish(LoginResult result) {
          super.onFinish(result);
          assertThat(result.getCode(), equalTo(SUCCESS));
          app.getEditTokenStorage().get(TEST_WIKI_SITE, callback);
        }
      };

  private EditTokenStorage.TokenRetrievedCallback callback =
      new EditTokenStorage.TokenRetrievedCallback() {
        @Override
        public void onTokenRetrieved(String token) {
          assertThat(token.equals("+\\"), is(false));
          completionLatch.countDown();
        }

        @Override
        public void onTokenFailed(Throwable caught) {
          throw new RuntimeException(caught);
        }
      };

  private void runOnMainSync(Runnable r) {
    getInstrumentation().runOnMainSync(r);
  }

  private static String getString(@StringRes int id) {
    return getInstrumentation().getContext().getString(id);
  }
}
  public ToCHandler(
      final AppCompatActivity activity,
      final WikiDrawerLayout slidingPane,
      final CommunicationBridge bridge) {
    this.parentActivity = activity;
    this.bridge = bridge;
    this.slidingPane = slidingPane;

    this.tocList = (ConfigurableListView) slidingPane.findViewById(R.id.page_toc_list);
    ((FrameLayout.LayoutParams) tocList.getLayoutParams())
        .setMargins(0, getContentTopOffsetPx(activity), 0, 0);
    this.tocProgress = (ProgressBar) slidingPane.findViewById(R.id.page_toc_in_progress);

    bridge.addListener(
        "currentSectionResponse",
        new CommunicationBridge.JSEventListener() {
          @Override
          public void onMessage(String messageType, JSONObject messagePayload) {
            int sectionID = messagePayload.optInt("sectionID");
            Log.d("Wikipedia", "current section is " + sectionID);
            if (tocList.getAdapter() == null) {
              return;
            }
            int itemToSelect = 0;
            // Find the list item that corresponds to the returned sectionID.
            // Start with index 1 of the list adapter, since index 0 is the header view,
            // and won't have a Section object associated with it.
            // And end with the second-to-last section, since the last section is the
            // artificial Read More section, and unknown to the WebView.
            // The lead section (id 0) will automatically fall through the loop.
            for (int i = 1; i < tocList.getAdapter().getCount() - 1; i++) {
              if (((Section) tocList.getAdapter().getItem(i)).getId() <= sectionID) {
                itemToSelect = i;
              } else {
                break;
              }
            }
            tocList.setItemChecked(itemToSelect, true);
            tocList.smoothScrollToPosition(itemToSelect);
          }
        });

    headerView =
        (TextView)
            LayoutInflater.from(tocList.getContext())
                .inflate(R.layout.header_toc_list, null, false);
    tocList.addHeaderView(headerView);

    // create a dummy funnel, in case the drawer is pulled out before a page is loaded.
    funnel =
        new ToCInteractionFunnel(
            WikipediaApp.getInstance(), WikipediaApp.getInstance().getSite(), 0, 0);

    slidingPane.setDrawerListener(
        new DrawerLayout.SimpleDrawerListener() {
          private boolean sectionRequested = false;

          @Override
          public void onDrawerOpened(View drawerView) {
            super.onDrawerOpened(drawerView);
            parentActivity.supportInvalidateOptionsMenu();
            funnel.logOpen();
            wasClicked = false;
          }

          @Override
          public void onDrawerClosed(View drawerView) {
            super.onDrawerClosed(drawerView);
            parentActivity.supportInvalidateOptionsMenu();
            if (!wasClicked) {
              funnel.logClose();
            }
            sectionRequested = false;
          }

          @Override
          public void onDrawerSlide(View drawerView, float slideOffset) {
            super.onDrawerSlide(drawerView, slideOffset);
            // make sure the ActionBar is showing
            ((PageActivity) parentActivity).showToolbar();
            ((PageActivity) parentActivity)
                .getSearchBarHideHandler()
                .setForceNoFade(slideOffset != 0);
            // request the current section to highlight, if we haven't yet
            if (!sectionRequested) {
              bridge.sendMessage("requestCurrentSection", new JSONObject());
              sectionRequested = true;
            }
          }
        });
  }
 private void showTocOnboarding() {
   View tocButton = parentActivity.findViewById(R.id.floating_toc_button);
   ToolTipUtil.showToolTip(
       parentActivity, tocButton, R.layout.inflate_tool_tip_toc_button, ToolTip.Position.CENTER);
   WikipediaApp.getInstance().getOnboardingStateMachine().setTocTutorial();
 }
 public RbPageService(final Site site) {
   responseHeaderHandler = WikipediaApp.getInstance().getWikipediaZeroHandler();
   webService = RbPageEndpointsCache.INSTANCE.getRbPageEndpoints(site);
 }
 // TODO: use getResources().getDimensionPixelSize()?  Define leadImageWidth with px, not dp?
 public static int calculateLeadImageWidth() {
   Resources res = WikipediaApp.getInstance().getResources();
   return (int) (res.getDimension(R.dimen.leadImageWidth) / res.getDisplayMetrics().density);
 }
 @Override
 public void onFinish(LoginResult result) {
   super.onFinish(result);
   assertThat(result.getCode(), equalTo(SUCCESS));
   app.getEditTokenStorage().get(TEST_WIKI_SITE, callback);
 }
 public void setPageHeight(int height) {
   this.pageHeight = (int) (height * app.getScreenDensity());
 }
  private void loadPageOnWebViewReady(boolean tryFromCache) {
    // stage any section-specific link target from the title, since the title may be
    // replaced (normalized)
    sectionTargetFromTitle = model.getTitle().getFragment();

    Utils.setupDirectionality(
        model.getTitle().getSite().getLanguageCode(), Locale.getDefault().getLanguage(), bridge);

    // hide the native top and bottom components...
    leadImagesHandler.hide();
    bottomContentHandler.hide();
    bottomContentHandler.setTitle(model.getTitle());

    if (model.getCurEntry().getSource() == HistoryEntry.SOURCE_SAVED_PAGE) {
      state = STATE_NO_FETCH;
      loadSavedPage();
    } else if (tryFromCache) {
      // is this page in cache??
      app.getPageCache()
          .get(
              model.getTitleOriginal(),
              pageSequenceNum,
              new PageCache.CacheGetListener() {
                @Override
                public void onGetComplete(Page page, int sequence) {
                  if (sequence != pageSequenceNum) {
                    return;
                  }
                  if (page != null) {
                    Log.d(
                        TAG, "Using page from cache: " + model.getTitleOriginal().getDisplayText());
                    model.setPage(page);
                    model.setTitle(page.getTitle());
                    // load the current title's thumbnail from sqlite
                    updateThumbnail(
                        PageImage.PERSISTENCE_HELPER.getImageUrlForTitle(app, model.getTitle()));
                    // Save history entry...
                    new SaveHistoryTask(model.getCurEntry(), app).execute();
                    // don't re-cache the page after loading.
                    cacheOnComplete = false;
                    state = STATE_COMPLETE_FETCH;
                    setState(state);
                    performActionForState(state);
                  } else {
                    // page isn't in cache, so fetch it from the network...
                    loadPageFromNetwork();
                  }
                }

                @Override
                public void onGetError(Throwable e, int sequence) {
                  Log.e(TAG, "Failed to get page from cache.", e);
                  if (sequence != pageSequenceNum) {
                    return;
                  }
                  // something failed when loading it from cache, so fetch it from network...
                  loadPageFromNetwork();
                }
              });
    } else {
      loadPageFromNetwork();
    }
  }