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

Commit 1a4cf445 authored by Adam Powell's avatar Adam Powell Committed by Android (Google) Code Review
Browse files

Merge "Pinning components in ChooserActivity, take 2"

parents 5f651eef 2388251f
Loading
Loading
Loading
Loading
+64 −9
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.LabeledIntent;
import android.content.pm.PackageManager;
@@ -35,6 +36,7 @@ import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -68,6 +70,7 @@ import com.android.internal.R;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -91,6 +94,11 @@ public class ChooserActivity extends ResolverActivity {
    private ChooserListAdapter mChooserListAdapter;
    private ChooserRowAdapter mChooserRowAdapter;

    private SharedPreferences mPinnedSharedPrefs;
    private static final float PINNED_TARGET_SCORE_BOOST = 1000.f;
    private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
    private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";

    private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();

    private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
@@ -207,12 +215,30 @@ public class ChooserActivity extends ResolverActivity {
        mRefinementIntentSender = intent.getParcelableExtra(
                Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
        setSafeForwardingMode(true);

        mPinnedSharedPrefs = getPinnedSharedPrefs(this);
        super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
                null, false);

        MetricsLogger.action(this, MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN);
    }

    static SharedPreferences getPinnedSharedPrefs(Context context) {
        // The code below is because in the android:ui process, no one can hear you scream.
        // The package info in the context isn't initialized in the way it is for normal apps,
        // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
        // build the path manually below using the same policy that appears in ContextImpl.
        // This fails silently under the hood if there's a problem, so if we find ourselves in
        // the case where we don't have access to credential encrypted storage we just won't
        // have our pinned target info.
        final File prefsFile = new File(new File(
                Environment.getDataUserCredentialEncryptedPackageDirectory(null,
                        context.getUserId(), context.getPackageName()),
                "shared_prefs"),
                PINNED_SHARED_PREFS_NAME + ".xml");
        return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
@@ -243,7 +269,7 @@ public class ChooserActivity extends ResolverActivity {
    }

    @Override
    void onActivityStarted(TargetInfo cti) {
    public void onActivityStarted(TargetInfo cti) {
        if (mChosenComponentSender != null) {
            final ComponentName target = cti.getResolvedComponentName();
            if (target != null) {
@@ -259,7 +285,7 @@ public class ChooserActivity extends ResolverActivity {
    }

    @Override
    void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
    public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
            boolean alwaysUseOption) {
        final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
        mChooserListAdapter = (ChooserListAdapter) adapter;
@@ -272,17 +298,17 @@ public class ChooserActivity extends ResolverActivity {
    }

    @Override
    int getLayoutResource() {
    public int getLayoutResource() {
        return R.layout.chooser_grid;
    }

    @Override
    boolean shouldGetActivityMetadata() {
    public boolean shouldGetActivityMetadata() {
        return true;
    }

    @Override
    boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
    public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
        final Intent intent = target.getResolvedIntent();
        final ResolveInfo resolve = target.getResolveInfo();

@@ -299,6 +325,16 @@ public class ChooserActivity extends ResolverActivity {
        return false;
    }

    @Override
    public void showTargetDetails(ResolveInfo ri) {
        ComponentName name = ri.activityInfo.getComponentName();
        boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
        ResolverTargetActionsDialogFragment f =
                new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
                        name, pinned);
        f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
    }

    private void modifyTargetIntent(Intent in) {
        final String action = in.getAction();
        if (Intent.ACTION_SEND.equals(action) ||
@@ -340,7 +376,7 @@ public class ChooserActivity extends ResolverActivity {
    }

    @Override
    void startSelected(int which, boolean always, boolean filtered) {
    public void startSelected(int which, boolean always, boolean filtered) {
        super.startSelected(which, always, filtered);

        if (mChooserListAdapter != null) {
@@ -471,7 +507,7 @@ public class ChooserActivity extends ResolverActivity {
        mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
    }

    void onSetupVoiceInteraction() {
    public void onSetupVoiceInteraction() {
        // Do nothing. We'll send the voice stuff ourselves.
    }

@@ -543,7 +579,7 @@ public class ChooserActivity extends ResolverActivity {
    }

    @Override
    ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
    public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
            Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
            boolean filterLastUsed) {
        final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
@@ -711,6 +747,11 @@ public class ChooserActivity extends ResolverActivity {
            }
            return results;
        }

        @Override
        public boolean isPinned() {
            return mSourceInfo != null ? mSourceInfo.isPinned() : false;
        }
    }

    public class ChooserListAdapter extends ResolveListAdapter {
@@ -776,6 +817,20 @@ public class ChooserActivity extends ResolverActivity {
            return false;
        }

        @Override
        public boolean isComponentPinned(ComponentName name) {
            return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
        }

        @Override
        public float getScore(DisplayResolveInfo target) {
            float score = super.getScore(target);
            if (target.isPinned()) {
                score += PINNED_TARGET_SCORE_BOOST;
            }
            return score;
        }

        @Override
        public View onCreateView(ViewGroup parent) {
            return mInflater.inflate(
@@ -1121,7 +1176,7 @@ public class ChooserActivity extends ResolverActivity {
                v.setOnLongClickListener(new OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View v) {
                        showAppDetails(
                        showTargetDetails(
                                mChooserListAdapter.resolveInfoForPosition(
                                        holder.itemIndices[column], true));
                        return true;
+68 −34
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.app.ActivityThread;
import android.app.VoiceInteractor.PickOptionRequest;
import android.app.VoiceInteractor.PickOptionRequest.Option;
import android.app.VoiceInteractor.Prompt;
import android.content.pm.ComponentInfo;
import android.os.AsyncTask;
import android.provider.Settings;
import android.text.TextUtils;
@@ -336,12 +337,12 @@ public class ResolverActivity extends Activity {
    /**
     * Perform any initialization needed for voice interaction.
     */
    void onSetupVoiceInteraction() {
    public void onSetupVoiceInteraction() {
        // Do it right now. Subclasses may delay this and send it later.
        sendVoiceChoicesIfNeeded();
    }

    void sendVoiceChoicesIfNeeded() {
    public void sendVoiceChoicesIfNeeded() {
        if (!isVoiceInteraction()) {
            // Clearly not needed.
            return;
@@ -382,7 +383,7 @@ public class ResolverActivity extends Activity {
        return null;
    }

    int getLayoutResource() {
    public int getLayoutResource() {
        return R.layout.resolver_list;
    }

@@ -591,7 +592,7 @@ public class ResolverActivity extends Activity {
                mAlwaysUseOption);
    }

    void startSelected(int which, boolean always, boolean filtered) {
    public void startSelected(int which, boolean always, boolean filtered) {
        if (isFinishing()) {
            return;
        }
@@ -761,7 +762,7 @@ public class ResolverActivity extends Activity {
        return true;
    }

    void safelyStartActivity(TargetInfo cti) {
    public void safelyStartActivity(TargetInfo cti) {
        // If needed, show that intent is forwarded
        // from managed profile to owner or other way around.
        if (mProfileSwitchMessageId != -1) {
@@ -791,26 +792,26 @@ public class ResolverActivity extends Activity {
        }
    }

    void onActivityStarted(TargetInfo cti) {
    public void onActivityStarted(TargetInfo cti) {
        // Do nothing
    }

    boolean shouldGetActivityMetadata() {
    public boolean shouldGetActivityMetadata() {
        return false;
    }

    boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
    public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
        return true;
    }

    void showAppDetails(ResolveInfo ri) {
    public void showTargetDetails(ResolveInfo ri) {
        Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
                .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
        startActivity(in);
    }

    ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
    public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
            Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
            boolean filterLastUsed) {
        return new ResolveListAdapter(context, payloadIntents, initialIntents, rList,
@@ -820,7 +821,7 @@ public class ResolverActivity extends Activity {
    /**
     * Returns true if the activity is finishing and creation should halt
     */
    boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
    public boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
            List<ResolveInfo> rList, boolean alwaysUseOption) {
        // The last argument of createAdapter is whether to do special handling
        // of the last used choice to highlight it in the list.  We need to always
@@ -867,7 +868,7 @@ public class ResolverActivity extends Activity {
        return false;
    }

    void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
    public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
            boolean alwaysUseOption) {
        final boolean useHeader = adapter.hasFilteredItem();
        final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
@@ -898,7 +899,7 @@ public class ResolverActivity extends Activity {
                && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName);
    }

    final class DisplayResolveInfo implements TargetInfo {
    public final class DisplayResolveInfo implements TargetInfo {
        private final ResolveInfo mResolveInfo;
        private final CharSequence mDisplayLabel;
        private Drawable mDisplayIcon;
@@ -906,8 +907,9 @@ public class ResolverActivity extends Activity {
        private final CharSequence mExtendedInfo;
        private final Intent mResolvedIntent;
        private final List<Intent> mSourceIntents = new ArrayList<>();
        private boolean mPinned;

        DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
        public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
                CharSequence pInfo, Intent pOrigIntent) {
            mSourceIntents.add(originalIntent);
            mResolveInfo = pri;
@@ -932,6 +934,7 @@ public class ResolverActivity extends Activity {
            mExtendedInfo = other.mExtendedInfo;
            mResolvedIntent = new Intent(other.mResolvedIntent);
            mResolvedIntent.fillIn(fillInIntent, flags);
            mPinned = other.mPinned;
        }

        public ResolveInfo getResolveInfo() {
@@ -1026,6 +1029,15 @@ public class ResolverActivity extends Activity {
            activity.startActivityAsUser(mResolvedIntent, options, user);
            return false;
        }

        @Override
        public boolean isPinned() {
            return mPinned;
        }

        public void setPinned(boolean pinned) {
            mPinned = pinned;
        }
    }

    /**
@@ -1039,7 +1051,7 @@ public class ResolverActivity extends Activity {
         *
         * @return the resolved intent for this target
         */
        public Intent getResolvedIntent();
        Intent getResolvedIntent();

        /**
         * Get the resolved component name that represents this target. Note that this may not
@@ -1048,7 +1060,7 @@ public class ResolverActivity extends Activity {
         *
         * @return the resolved ComponentName for this target
         */
        public ComponentName getResolvedComponentName();
        ComponentName getResolvedComponentName();

        /**
         * Start the activity referenced by this target.
@@ -1057,7 +1069,7 @@ public class ResolverActivity extends Activity {
         * @param options ActivityOptions bundle
         * @return true if the start completed successfully
         */
        public boolean start(Activity activity, Bundle options);
        boolean start(Activity activity, Bundle options);

        /**
         * Start the activity referenced by this target as if the ResolverActivity's caller
@@ -1068,7 +1080,7 @@ public class ResolverActivity extends Activity {
         * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
         * @return true if the start completed successfully
         */
        public boolean startAsCaller(Activity activity, Bundle options, int userId);
        boolean startAsCaller(Activity activity, Bundle options, int userId);

        /**
         * Start the activity referenced by this target as a given user.
@@ -1078,7 +1090,7 @@ public class ResolverActivity extends Activity {
         * @param user handle for the user to start the activity as
         * @return true if the start completed successfully
         */
        public boolean startAsUser(Activity activity, Bundle options, UserHandle user);
        boolean startAsUser(Activity activity, Bundle options, UserHandle user);

        /**
         * Return the ResolveInfo about how and why this target matched the original query
@@ -1086,14 +1098,14 @@ public class ResolverActivity extends Activity {
         *
         * @return ResolveInfo representing this target's match
         */
        public ResolveInfo getResolveInfo();
        ResolveInfo getResolveInfo();

        /**
         * Return the human-readable text label for this target.
         *
         * @return user-visible target label
         */
        public CharSequence getDisplayLabel();
        CharSequence getDisplayLabel();

        /**
         * Return any extended info for this target. This may be used to disambiguate
@@ -1101,35 +1113,40 @@ public class ResolverActivity extends Activity {
         *
         * @return human-readable disambig string or null if none present
         */
        public CharSequence getExtendedInfo();
        CharSequence getExtendedInfo();

        /**
         * @return The drawable that should be used to represent this target
         */
        public Drawable getDisplayIcon();
        Drawable getDisplayIcon();

        /**
         * @return The (small) icon to badge the target with
         */
        public Drawable getBadgeIcon();
        Drawable getBadgeIcon();

        /**
         * @return The content description for the badge icon
         */
        public CharSequence getBadgeContentDescription();
        CharSequence getBadgeContentDescription();

        /**
         * Clone this target with the given fill-in information.
         */
        public TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
        TargetInfo cloneFilledIn(Intent fillInIntent, int flags);

        /**
         * @return the list of supported source intents deduped against this single target
         */
        public List<Intent> getAllSourceIntents();
        List<Intent> getAllSourceIntents();

        /**
         * @return true if this target should be pinned to the front by the request of the user
         */
        boolean isPinned();
    }

    class ResolveListAdapter extends BaseAdapter {
    public class ResolveListAdapter extends BaseAdapter {
        private final List<Intent> mIntents;
        private final Intent[] mInitialIntents;
        private final List<ResolveInfo> mBaseResolveList;
@@ -1376,9 +1393,12 @@ public class ResolverActivity extends Activity {
                    }
                }
                if (!found) {
                    into.add(new ResolvedComponentInfo(new ComponentName(
                            newInfo.activityInfo.packageName, newInfo.activityInfo.name),
                            intent, newInfo));
                    final ComponentName name = new ComponentName(
                            newInfo.activityInfo.packageName, newInfo.activityInfo.name);
                    final ResolvedComponentInfo rci = new ResolvedComponentInfo(name,
                            intent, newInfo);
                    rci.setPinned(isComponentPinned(name));
                    into.add(rci);
                }
            }
        }
@@ -1454,6 +1474,7 @@ public class ResolverActivity extends Activity {
            final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent);
            final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel,
                    extraInfo, replaceIntent);
            dri.setPinned(rci.isPinned());
            addResolveInfo(dri);
            if (replaceIntent == intent) {
                // Only add alternates if we didn't get a specific replacement from
@@ -1537,11 +1558,11 @@ public class ResolverActivity extends Activity {
            return false;
        }

        protected int getDisplayResolveInfoCount() {
        public int getDisplayResolveInfoCount() {
            return mDisplayList.size();
        }

        protected DisplayResolveInfo getDisplayResolveInfo(int index) {
        public DisplayResolveInfo getDisplayResolveInfo(int index) {
            // Used to query services. We only query services for primary targets, not alternates.
            return mDisplayList.get(index);
        }
@@ -1571,6 +1592,10 @@ public class ResolverActivity extends Activity {
            return !TextUtils.isEmpty(info.getExtendedInfo());
        }

        public boolean isComponentPinned(ComponentName name) {
            return false;
        }

        public final void bindView(int position, View view) {
            onBindView(view, getItem(position));
        }
@@ -1607,6 +1632,7 @@ public class ResolverActivity extends Activity {

    static final class ResolvedComponentInfo {
        public final ComponentName name;
        private boolean mPinned;
        private final List<Intent> mIntents = new ArrayList<>();
        private final List<ResolveInfo> mResolveInfos = new ArrayList<>();

@@ -1649,6 +1675,14 @@ public class ResolverActivity extends Activity {
            }
            return -1;
        }

        public boolean isPinned() {
            return mPinned;
        }

        public void setPinned(boolean pinned) {
            mPinned = pinned;
        }
    }

    static class ViewHolder {
@@ -1702,7 +1736,7 @@ public class ResolverActivity extends Activity {
                return false;
            }
            ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true);
            showAppDetails(ri);
            showTargetDetails(ri);
            return true;
        }

+22 −10
Original line number Diff line number Diff line
@@ -47,8 +47,8 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {

    private static final boolean DEBUG = false;

    // Two weeks
    private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
    // One week
    private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 7;

    private static final long RECENCY_TIME_PERIOD = 1000 * 60 * 60 * 12;

@@ -171,6 +171,17 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
            }
        }

        final boolean lPinned = lhsp.isPinned();
        final boolean rPinned = rhsp.isPinned();

        if (lPinned && !rPinned) {
            return -1;
        } else if (!lPinned && rPinned) {
            return 1;
        }

        // Pinned items stay stable within a normal lexical sort and ignore scoring.
        if (!lPinned && !rPinned) {
            if (mStats != null) {
                final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
                        lhs.activityInfo.packageName, lhs.activityInfo.name));
@@ -182,6 +193,7 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
                    return diff > 0 ? 1 : -1;
                }
            }
        }

        CharSequence  sa = lhs.loadLabel(mPm);
        if (sa == null) sa = lhs.activityInfo.name;
+98 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source 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.internal.app;

import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;

import com.android.internal.R;

/**
 * Shows a dialog with actions to take on a chooser target
 */
public class ResolverTargetActionsDialogFragment extends DialogFragment
        implements DialogInterface.OnClickListener {
    private static final String NAME_KEY = "componentName";
    private static final String PINNED_KEY = "pinned";
    private static final String TITLE_KEY = "title";

    // Sync with R.array.resolver_target_actions_* resources
    private static final int TOGGLE_PIN_INDEX = 0;
    private static final int APP_INFO_INDEX = 1;

    public ResolverTargetActionsDialogFragment() {
    }

    public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name,
            boolean pinned) {
        Bundle args = new Bundle();
        args.putCharSequence(TITLE_KEY, title);
        args.putParcelable(NAME_KEY, name);
        args.putBoolean(PINNED_KEY, pinned);
        setArguments(args);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        final Bundle args = getArguments();
        final int itemRes = args.getBoolean(PINNED_KEY, false)
                ? R.array.resolver_target_actions_unpin
                : R.array.resolver_target_actions_pin;
        return new Builder(getContext())
                .setCancelable(true)
                .setItems(itemRes, this)
                .setTitle(args.getCharSequence(TITLE_KEY))
                .create();
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        final Bundle args = getArguments();
        ComponentName name = args.getParcelable(NAME_KEY);
        switch (which) {
            case TOGGLE_PIN_INDEX:
                SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
                final String key = name.flattenToString();
                boolean currentVal = sp.getBoolean(name.flattenToString(), false);
                if (currentVal) {
                    sp.edit().remove(key).apply();
                } else {
                    sp.edit().putBoolean(key, true).apply();
                }

                // Force the chooser to requery and resort things
                getActivity().recreate();
                break;
            case APP_INFO_INDEX:
                Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                        .setData(Uri.fromParts("package", name.getPackageName(), null))
                        .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
                startActivity(in);
                break;
        }
        dismiss();
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -402,4 +402,15 @@
        <item>@color/Red_700</item>
    </array>

    <!-- Used in ResolverTargetActionsDialogFragment -->
    <string-array name="resolver_target_actions_pin">
        <item>@string/pin_target</item>
        <item>@string/app_info</item>
    </string-array>

    <string-array name="resolver_target_actions_unpin">
        <item>@string/unpin_target</item>
        <item>@string/app_info</item>
    </string-array>

</resources>
Loading