Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 21bf85c4 authored by Michael W's avatar Michael W
Browse files

Dialer: Add a helpline phone book

* Expose the sensitive numbers to the user so they can easily call one
  of them
* Make sure to leave as few traces of access as possible:
  * Calling a number closes the helpline activity
  * Opening a link closes the helpline activity
  * Before opening a link, a warning about browser history appears

* This makes use of a changed xml-format, which now allows adding
  various things:
<item>
    <categories>violence|human trafficking</categories>
    <name>Some NGO or whatever</name>
    <number>1234</number>
    <anything>anything else</anything>
</item>

Change-Id: I952bf10ee9516b83e595d8a19450696b303393b6
parent 62924a3c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -133,6 +133,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
	libbackup \
	libphonenumber \
	volley \
	org.lineageos.lib.phone

LOCAL_STATIC_ANDROID_LIBRARIES := \
	android-support-core-ui \
+3 −1
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright (C) 2013-2014 The CyanogenMod Project
     Copyright (C) 2018 The LineageOS Project
     Copyright (C) 2018-2021 The LineageOS Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -43,4 +43,6 @@
    <string name="call_via_dialog_title">Call via\u2026</string>

    <string name="call_log_stats_title">Statistics</string>

    <string name="action_menu_helplines">Helplines</string>
</resources>
+36 −0
Original line number Diff line number Diff line
<!--
    Copyright (C) 2019-2021 The LineageOS Project

    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
 -->
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.dialer.helplines">

    <application>

        <activity
            android:name=".HelplineActivity"
            android:autoRemoveFromRecents="true"
            android:label="@string/helplines_name"
            android:theme="@android:style/Theme.DeviceDefault.Settings"
            android:excludeFromRecents="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="header"/>
            </intent-filter>
        </activity>
    </application>

</manifest>
+245 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019-2021 The LineageOS Project
 *
 * 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.android.dialer.helplines;

import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.android.dialer.R;
import com.android.dialer.app.calllog.IntentProvider;
import com.android.dialer.helplines.utils.HelplineUtils;

import java.util.List;

import static android.graphics.Paint.UNDERLINE_TEXT_FLAG;

public class HelplineActivity extends Activity {

    public static final String SHARED_PREFERENCES_KEY = "com.android.dialer.prefs";

    private static final String KEY_FIRST_LAUNCH = "pref_first_launch";

    private RecyclerView mRecyclerView;
    private LinearLayout mLoadingView;
    private LinearLayout mEmptyView;
    private ProgressBar mProgressBar;

    private HelplineAdapter mAdapter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstance) {
        super.onCreate(savedInstance);

        ActionBar actionBar = getActionBar();
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
        }

        setContentView(R.layout.activity_helplines);
        mRecyclerView = findViewById(R.id.helplines_list);
        mLoadingView = findViewById(R.id.helplines_loading);
        mEmptyView = findViewById(R.id.empty_view);
        mProgressBar = findViewById(R.id.helplines_progress_bar);

        mAdapter = new HelplineAdapter(getResources(), mListener);

        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setAdapter(mAdapter);

        showUi();
    }

    @Override
    protected void onPause() {
        super.onPause();
        finish();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater menuInflater = getMenuInflater();
        menuInflater.inflate(R.menu.menu_helplines, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == android.R.id.home) {
            finish();
            return true;
        } else if (id == R.id.menu_helpline_help) {
            showHelp(true);
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    private void showUi() {
        mLoadingView.setVisibility(View.VISIBLE);

        showHelp(false);
        SubscriptionManager subManager = getSystemService(SubscriptionManager.class);
        new LoadHelplinesTask(getResources(), subManager, mCallback).execute();
    }

    private void showHelp(boolean forceShow) {
        SharedPreferences preferenceManager = getPrefs();
        if (!forceShow && preferenceManager.getBoolean(KEY_FIRST_LAUNCH, false)) {
            return;
        }

        preferenceManager.edit()
                .putBoolean(KEY_FIRST_LAUNCH, true)
                .apply();

        new AlertDialog.Builder(this)
                .setTitle(R.string.helplines_help_title)
                .setMessage(R.string.helplines_help_message)
                .setPositiveButton(android.R.string.ok, null)
                .setNeutralButton(R.string.helpline_button_more, (dialog, which) -> {
                    showMoreInfo(); })
                .show();
    }

    private void showMoreInfo() {
        new AlertDialog.Builder(this)
                .setMessage(R.string.helplines_help_more_message)
                .setPositiveButton(android.R.string.ok, null)
                .show();
    }

    public SharedPreferences getPrefs() {
        return this.getSharedPreferences(SHARED_PREFERENCES_KEY,
                Context.MODE_PRIVATE);
    }

    private LoadHelplinesTask.Callback mCallback = new LoadHelplinesTask.Callback () {
        @Override
        public void onLoadListProgress(int progress) {
            mProgressBar.setProgress(progress);
        }

        @Override
        public void onLoadCompleted(List<HelplineItem> result) {
            mLoadingView.setVisibility(View.GONE);
            if (result.size() == 0) {
                mEmptyView.setVisibility(View.VISIBLE);
            } else {
                mRecyclerView.setVisibility(View.VISIBLE);
            }
            mAdapter.update(result);
        }
    };

    private HelplineAdapter.Listener mListener = new HelplineAdapter.Listener() {
        private AlertDialog mDialog;

        @Override
        public void initiateCall(@NonNull String number) {
            IntentProvider provider = IntentProvider.getReturnCallIntentProvider(number);
            Intent intent = provider.getClickIntent(HelplineActivity.this);
            // Start the call and finish this activity - we don't want to leave traces of the call
            startActivity(intent);
            finish();
        }

        @Override
        public void onItemClicked(@NonNull HelplineItem item) {
            LayoutInflater inflater = LayoutInflater.from(HelplineActivity.this);
            final View dialogView = inflater.inflate(R.layout.dialog_helpline_details, null);

            fillOrHideDialogRow(item.getName(), dialogView, R.id.name_title, R.id.name);
            fillOrHideDialogRow(item.get("organization"), dialogView, R.id.org_title, R.id.org);
            fillOrHideDialogRow(HelplineUtils.getCategories(getResources(), item),
                    dialogView, R.id.categories_title, R.id.categories);
            fillOrHideDialogRow(item.get("number"), dialogView, R.id.number_title, R.id.number);
            fillOrHideDialogRow(item.get("website"), dialogView, R.id.website_title, R.id.website,
                    true);

            mDialog = new AlertDialog.Builder(HelplineActivity.this)
                    .setView(dialogView)
                    .setPositiveButton(android.R.string.ok, null)
                    .show();
        }

        private void fillOrHideDialogRow(String content, View dialog, int headerViewId,
                                         int contentViewId) {
            fillOrHideDialogRow(content, dialog, headerViewId, contentViewId, false);
        }

        private void fillOrHideDialogRow(String content, View dialogView, int headerViewId,
                                         int contentViewId, boolean isUrl) {
            if (dialogView == null) {
                return;
            }
            TextView headerView = dialogView.findViewById(headerViewId);
            TextView contentView = dialogView.findViewById(contentViewId);
            if (headerView == null || contentView == null) {
                return;
            }
            if (TextUtils.isEmpty(content)) {
                headerView.setVisibility(View.GONE);
                contentView.setVisibility(View.GONE);
                return;
            }

            contentView.setText(content);
            if (isUrl) {
                contentView.setPaintFlags(contentView.getPaintFlags() | UNDERLINE_TEXT_FLAG);
                // We want to warn the user that visiting a link might leave traces
                contentView.setOnClickListener(v -> {
                            new AlertDialog.Builder(HelplineActivity.this)
                                    .setTitle(R.string.helpline_browser_history_title)
                                    .setMessage(R.string.helpline_browser_history_message)
                                    .setPositiveButton(android.R.string.ok, (dlg, which) -> {
                                        Intent i = new Intent(Intent.ACTION_VIEW);
                                        i.setData(Uri.parse(content));
                                        mDialog.dismiss();
                                        dlg.dismiss();
                                        startActivity(i);
                                        finish(); // Finish this activity to get rid of own traces
                                    })
                                    .setNegativeButton(android.R.string.cancel, null)
                                    .show();
                        }
                );
            }
        }
    };
}
+158 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019-2021 The LineageOS Project
 *
 * 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.android.dialer.helplines;

import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.support.v7.util.DiffUtil;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.android.dialer.R;
import com.android.dialer.helplines.utils.HelplineUtils;

import java.util.ArrayList;
import java.util.List;

class HelplineAdapter extends RecyclerView.Adapter<HelplineAdapter.ViewHolder> {

    private Resources mResources;
    private List<HelplineItem> mList = new ArrayList<>();
    private Listener mListener;

    HelplineAdapter(Resources resources, Listener listener) {
        mResources = resources;
        mListener = listener;
    }

    public void update(List<HelplineItem> list) {
        DiffUtil.DiffResult result = DiffUtil.calculateDiff(new Callback(mList, list));
        mList = list;
        result.dispatchUpdatesTo(this);
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) {
        return new ViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_helpline, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
        viewHolder.bind(mList.get(i));
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }

    public interface Listener {
        void initiateCall(@NonNull String number);

        void onItemClicked(@NonNull HelplineItem item);
    }

    private static class Callback extends DiffUtil.Callback {
        List<HelplineItem> mOldList;
        List<HelplineItem> mNewList;

        public Callback(List<HelplineItem> oldList,
                        List<HelplineItem> newList) {
            mOldList = oldList;
            mNewList = newList;
        }

        @Override
        public int getOldListSize() {
            return mOldList.size();
        }

        @Override
        public int getNewListSize() {
            return mNewList.size();
        }

        @Override
        public boolean areItemsTheSame(int iOld, int iNew) {
            String oldNumber = mOldList.get(iOld).get("number");
            String newNumber = mOldList.get(iNew).get("number");
            return oldNumber.equals(newNumber);
        }

        @Override
        public boolean areContentsTheSame(int iOld, int iNew) {
            return false;
        }
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        private final View mItemView;
        private final TextView mLabelView;
        private final TextView mCategoriesView;
        private final TextView mLanguageView;
        private final ImageView mCallIcon;

        ViewHolder(@NonNull View itemView) {
            super(itemView);

            mItemView = itemView;
            mLabelView = itemView.findViewById(R.id.item_helpline_title);
            mCategoriesView = itemView.findViewById(R.id.item_helpline_categories);
            mLanguageView = itemView.findViewById(R.id.item_helpline_languages);
            mCallIcon = itemView.findViewById(R.id.item_helpline_call_icon);
        }

        void bind(HelplineItem item) {
            mItemView.setOnClickListener(v -> {
                mListener.onItemClicked(item);
            });

            String name = item.getName();
            if (!TextUtils.isEmpty(name)) {
                mLabelView.setText(name);
            } else {
                mLabelView.setText(R.string.unknown_helpline_name);
            }

            String categories = HelplineUtils.getCategories(mResources, item);
            if (!TextUtils.isEmpty(categories)) {
                mCategoriesView.setText(categories);
                mCategoriesView.setVisibility(View.VISIBLE);
            }

            String languages = HelplineUtils.getLanguages(mResources, item);
            if (!TextUtils.isEmpty(languages)) {
                mLanguageView.setVisibility(View.VISIBLE);
                mLanguageView.setText(languages);
            }

            String number = item.get("number");
            if (!TextUtils.isEmpty(number)) {
                mCallIcon.setVisibility(View.VISIBLE);
                mCallIcon.setOnClickListener(v -> {
                    mListener.initiateCall(number);
                });
            }
        }
    }
}
Loading