ProgramSelector.java revision dc7687a29b113276294dea9c0b58b7a1e2e6941f
1/**
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.hardware.radio;
18
19import android.annotation.IntDef;
20import android.annotation.IntRange;
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.annotation.SystemApi;
24import android.os.Parcel;
25import android.os.Parcelable;
26
27import java.lang.annotation.Retention;
28import java.lang.annotation.RetentionPolicy;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.List;
32import java.util.Objects;
33import java.util.stream.Stream;
34
35/**
36 * A set of identifiers necessary to tune to a given station.
37 *
38 * This can hold various identifiers, like
39 * - AM/FM frequency
40 * - HD Radio subchannel
41 * - DAB channel info
42 *
43 * The primary ID uniquely identifies a station and can be used for equality
44 * check. The secondary IDs are supplementary and can speed up tuning process,
45 * but the primary ID is sufficient (ie. after a full band scan).
46 *
47 * Two selectors with different secondary IDs, but the same primary ID are
48 * considered equal. In particular, secondary IDs vector may get updated for
49 * an entry on the program list (ie. when a better frequency for a given
50 * station is found).
51 *
52 * The primaryId of a given programType MUST be of a specific type:
53 * - AM, FM: RDS_PI if the station broadcasts RDS, AMFM_FREQUENCY otherwise;
54 * - AM_HD, FM_HD: HD_STATION_ID_EXT;
55 * - DAB: DAB_SIDECC;
56 * - DRMO: DRMO_SERVICE_ID;
57 * - SXM: SXM_SERVICE_ID;
58 * - VENDOR: VENDOR_PRIMARY.
59 * @hide
60 */
61@SystemApi
62public final class ProgramSelector implements Parcelable {
63    /** Invalid program type.
64     * @deprecated use {@link ProgramIdentifier} instead
65     */
66    @Deprecated
67    public static final int PROGRAM_TYPE_INVALID = 0;
68    /** Analogue AM radio (with or without RDS).
69     * @deprecated use {@link ProgramIdentifier} instead
70     */
71    @Deprecated
72    public static final int PROGRAM_TYPE_AM = 1;
73    /** analogue FM radio (with or without RDS).
74     * @deprecated use {@link ProgramIdentifier} instead
75     */
76    @Deprecated
77    public static final int PROGRAM_TYPE_FM = 2;
78    /** AM HD Radio.
79     * @deprecated use {@link ProgramIdentifier} instead
80     */
81    @Deprecated
82    public static final int PROGRAM_TYPE_AM_HD = 3;
83    /** FM HD Radio.
84     * @deprecated use {@link ProgramIdentifier} instead
85     */
86    @Deprecated
87    public static final int PROGRAM_TYPE_FM_HD = 4;
88    /** Digital audio broadcasting.
89     * @deprecated use {@link ProgramIdentifier} instead
90     */
91    @Deprecated
92    public static final int PROGRAM_TYPE_DAB = 5;
93    /** Digital Radio Mondiale.
94     * @deprecated use {@link ProgramIdentifier} instead
95     */
96    @Deprecated
97    public static final int PROGRAM_TYPE_DRMO = 6;
98    /** SiriusXM Satellite Radio.
99     * @deprecated use {@link ProgramIdentifier} instead
100     */
101    @Deprecated
102    public static final int PROGRAM_TYPE_SXM = 7;
103    /** Vendor-specific, not synced across devices.
104     * @deprecated use {@link ProgramIdentifier} instead
105     */
106    @Deprecated
107    public static final int PROGRAM_TYPE_VENDOR_START = 1000;
108    /** @deprecated use {@link ProgramIdentifier} instead */
109    @Deprecated
110    public static final int PROGRAM_TYPE_VENDOR_END = 1999;
111    /** @deprecated use {@link ProgramIdentifier} instead */
112    @Deprecated
113    @IntDef(prefix = { "PROGRAM_TYPE_" }, value = {
114        PROGRAM_TYPE_INVALID,
115        PROGRAM_TYPE_AM,
116        PROGRAM_TYPE_FM,
117        PROGRAM_TYPE_AM_HD,
118        PROGRAM_TYPE_FM_HD,
119        PROGRAM_TYPE_DAB,
120        PROGRAM_TYPE_DRMO,
121        PROGRAM_TYPE_SXM,
122    })
123    @IntRange(from = PROGRAM_TYPE_VENDOR_START, to = PROGRAM_TYPE_VENDOR_END)
124    @Retention(RetentionPolicy.SOURCE)
125    public @interface ProgramType {}
126
127    public static final int IDENTIFIER_TYPE_INVALID = 0;
128    /** kHz */
129    public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1;
130    /** 16bit */
131    public static final int IDENTIFIER_TYPE_RDS_PI = 2;
132    /**
133     * 64bit compound primary identifier for HD Radio.
134     *
135     * Consists of (from the LSB):
136     * - 32bit: Station ID number;
137     * - 4bit: HD_SUBCHANNEL;
138     * - 18bit: AMFM_FREQUENCY.
139     * The remaining bits should be set to zeros when writing on the chip side
140     * and ignored when read.
141     */
142    public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3;
143    /**
144     * HD Radio subchannel - a value of range 0-7.
145     *
146     * The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS),
147     * as opposed to HD Radio standard (where it's 1-based).
148     *
149     * @deprecated use IDENTIFIER_TYPE_HD_STATION_ID_EXT instead
150     */
151    @Deprecated
152    public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4;
153    /**
154     * 64bit additional identifier for HD Radio.
155     *
156     * Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not
157     * globally unique. To provide a best-effort solution, a short version of
158     * station name may be carried as additional identifier and may be used
159     * by the tuner hardware to double-check tuning.
160     *
161     * The name is limited to the first 8 A-Z0-9 characters (lowercase letters
162     * must be converted to uppercase). Encoded in little-endian ASCII:
163     * the first character of the name is the LSB.
164     *
165     * For example: "Abc" is encoded as 0x434241.
166     */
167    public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004;
168    /**
169     * @see {@link IDENTIFIER_TYPE_DAB_SID_EXT}
170     */
171    public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5;
172    /**
173     * 28bit compound primary identifier for Digital Audio Broadcasting.
174     *
175     * Consists of (from the LSB):
176     * - 16bit: SId;
177     * - 8bit: ECC code;
178     * - 4bit: SCIdS.
179     *
180     * SCIdS (Service Component Identifier within the Service) value
181     * of 0 represents the main service, while 1 and above represents
182     * secondary services.
183     *
184     * The remaining bits should be set to zeros when writing on the chip side
185     * and ignored when read.
186     */
187    public static final int IDENTIFIER_TYPE_DAB_SID_EXT = IDENTIFIER_TYPE_DAB_SIDECC;
188    /** 16bit */
189    public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6;
190    /** 12bit */
191    public static final int IDENTIFIER_TYPE_DAB_SCID = 7;
192    /** kHz */
193    public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8;
194    /** 24bit */
195    public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9;
196    /** kHz */
197    public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
198    /**
199     * 1: AM, 2:FM
200     * @deprecated use {@link IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
201     */
202    @Deprecated
203    public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
204    /** 32bit */
205    public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
206    /** 0-999 range */
207    public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
208    /**
209     * Primary identifier for vendor-specific radio technology.
210     * The value format is determined by a vendor.
211     *
212     * It must not be used in any other programType than corresponding VENDOR
213     * type between VENDOR_START and VENDOR_END (eg. identifier type 1015 must
214     * not be used in any program type other than 1015).
215     */
216    public static final int IDENTIFIER_TYPE_VENDOR_START = PROGRAM_TYPE_VENDOR_START;
217    /**
218     * @see {@link IDENTIFIER_TYPE_VENDOR_START}
219     */
220    public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END;
221    /**
222     * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_START} instead
223     */
224    @Deprecated
225    public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = IDENTIFIER_TYPE_VENDOR_START;
226    /**
227     * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_END} instead
228     */
229    @Deprecated
230    public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = IDENTIFIER_TYPE_VENDOR_END;
231    @IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = {
232        IDENTIFIER_TYPE_INVALID,
233        IDENTIFIER_TYPE_AMFM_FREQUENCY,
234        IDENTIFIER_TYPE_RDS_PI,
235        IDENTIFIER_TYPE_HD_STATION_ID_EXT,
236        IDENTIFIER_TYPE_HD_SUBCHANNEL,
237        IDENTIFIER_TYPE_HD_STATION_NAME,
238        IDENTIFIER_TYPE_DAB_SID_EXT,
239        IDENTIFIER_TYPE_DAB_SIDECC,
240        IDENTIFIER_TYPE_DAB_ENSEMBLE,
241        IDENTIFIER_TYPE_DAB_SCID,
242        IDENTIFIER_TYPE_DAB_FREQUENCY,
243        IDENTIFIER_TYPE_DRMO_SERVICE_ID,
244        IDENTIFIER_TYPE_DRMO_FREQUENCY,
245        IDENTIFIER_TYPE_DRMO_MODULATION,
246        IDENTIFIER_TYPE_SXM_SERVICE_ID,
247        IDENTIFIER_TYPE_SXM_CHANNEL,
248    })
249    @IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END)
250    @Retention(RetentionPolicy.SOURCE)
251    public @interface IdentifierType {}
252
253    private final @ProgramType int mProgramType;
254    private final @NonNull Identifier mPrimaryId;
255    private final @NonNull Identifier[] mSecondaryIds;
256    private final @NonNull long[] mVendorIds;
257
258    /**
259     * Constructor for ProgramSelector.
260     *
261     * It's not desired to modify selector objects, so all its fields are initialized at creation.
262     *
263     * Identifier lists must not contain any nulls, but can itself be null to be interpreted
264     * as empty list at object creation.
265     *
266     * @param programType type of a radio technology.
267     * @param primaryId primary program identifier.
268     * @param secondaryIds list of secondary program identifiers.
269     * @param vendorIds list of vendor-specific program identifiers.
270     */
271    public ProgramSelector(@ProgramType int programType, @NonNull Identifier primaryId,
272            @Nullable Identifier[] secondaryIds, @Nullable long[] vendorIds) {
273        if (secondaryIds == null) secondaryIds = new Identifier[0];
274        if (vendorIds == null) vendorIds = new long[0];
275        if (Stream.of(secondaryIds).anyMatch(id -> id == null)) {
276            throw new IllegalArgumentException("secondaryIds list must not contain nulls");
277        }
278        mProgramType = programType;
279        mPrimaryId = Objects.requireNonNull(primaryId);
280        mSecondaryIds = secondaryIds;
281        mVendorIds = vendorIds;
282    }
283
284    /**
285     * Type of a radio technology.
286     *
287     * @return program type.
288     * @deprecated use {@link getPrimaryId} instead
289     */
290    @Deprecated
291    public @ProgramType int getProgramType() {
292        return mProgramType;
293    }
294
295    /**
296     * Primary program identifier uniquely identifies a station and is used to
297     * determine equality between two ProgramSelectors.
298     *
299     * @return primary identifier.
300     */
301    public @NonNull Identifier getPrimaryId() {
302        return mPrimaryId;
303    }
304
305    /**
306     * Secondary program identifier is not required for tuning, but may make it
307     * faster or more reliable.
308     *
309     * @return secondary identifier list, must not be modified.
310     */
311    public @NonNull Identifier[] getSecondaryIds() {
312        return mSecondaryIds;
313    }
314
315    /**
316     * Looks up an identifier of a given type (either primary or secondary).
317     *
318     * If there are multiple identifiers if a given type, then first in order (where primary id is
319     * before any secondary) is selected.
320     *
321     * @param type type of identifier.
322     * @return identifier value, if found.
323     * @throws IllegalArgumentException, if not found.
324     */
325    public long getFirstId(@IdentifierType int type) {
326        if (mPrimaryId.getType() == type) return mPrimaryId.getValue();
327        for (Identifier id : mSecondaryIds) {
328            if (id.getType() == type) return id.getValue();
329        }
330        throw new IllegalArgumentException("Identifier " + type + " not found");
331    }
332
333    /**
334     * Looks up all identifier of a given type (either primary or secondary).
335     *
336     * Some identifiers may be provided multiple times, for example
337     * IDENTIFIER_TYPE_AMFM_FREQUENCY for FM Alternate Frequencies.
338     *
339     * @param type type of identifier.
340     * @return a list of identifiers, generated on each call. May be modified.
341     */
342    public @NonNull Identifier[] getAllIds(@IdentifierType int type) {
343        List<Identifier> out = new ArrayList<>();
344
345        if (mPrimaryId.getType() == type) out.add(mPrimaryId);
346        for (Identifier id : mSecondaryIds) {
347            if (id.getType() == type) out.add(id);
348        }
349
350        return out.toArray(new Identifier[out.size()]);
351    }
352
353    /**
354     * Vendor identifiers are passed as-is to the HAL implementation,
355     * preserving elements order.
356     *
357     * @return an array of vendor identifiers, must not be modified.
358     * @deprecated for HAL 1.x compatibility;
359     *             HAL 2.x uses standard primary/secondary lists for vendor IDs
360     */
361    @Deprecated
362    public @NonNull long[] getVendorIds() {
363        return mVendorIds;
364    }
365
366    /**
367     * Creates an equivalent ProgramSelector with a given secondary identifier preferred.
368     *
369     * Used to point to a specific physical identifier for technologies that may broadcast the same
370     * program on different channels. For example, with a DAB program broadcasted over multiple
371     * ensembles, the radio hardware may select the one with the strongest signal. The UI may select
372     * preferred ensemble though, so the radio hardware may try to use it in the first place.
373     *
374     * This is a best-effort hint for the tuner, not a guaranteed behavior.
375     *
376     * Setting the given secondary identifier as preferred means filtering out other secondary
377     * identifiers of its type and adding it to the list.
378     *
379     * @param preferred preferred secondary identifier
380     * @return a new ProgramSelector with a given secondary identifier preferred
381     */
382    public @NonNull ProgramSelector withSecondaryPreferred(@NonNull Identifier preferred) {
383        int preferredType = preferred.getType();
384        Identifier[] secondaryIds = Stream.concat(
385            // remove other identifiers of that type
386            Arrays.stream(mSecondaryIds).filter(id -> id.getType() != preferredType),
387            // add preferred identifier instead
388            Stream.of(preferred)).toArray(Identifier[]::new);
389
390        return new ProgramSelector(
391            mProgramType,
392            mPrimaryId,
393            secondaryIds,
394            mVendorIds
395        );
396    }
397
398    /**
399     * Builds new ProgramSelector for AM/FM frequency.
400     *
401     * @param band the band.
402     * @param frequencyKhz the frequency in kHz.
403     * @return new ProgramSelector object representing given frequency.
404     * @throws IllegalArgumentException if provided frequency is out of bounds.
405     */
406    public static @NonNull ProgramSelector createAmFmSelector(
407            @RadioManager.Band int band, int frequencyKhz) {
408        return createAmFmSelector(band, frequencyKhz, 0);
409    }
410
411    /**
412     * Checks, if a given AM/FM frequency is roughly valid and in correct unit.
413     *
414     * It does not check the range precisely: it may provide false positives, but not false
415     * negatives. In particular, it may be way off for certain regions.
416     * The main purpose is to avoid passing inproper units, ie. MHz instead of kHz.
417     *
418     * @param isAm true, if AM, false if FM.
419     * @param frequencyKhz the frequency in kHz.
420     * @return true, if the frequency is rougly valid.
421     */
422    private static boolean isValidAmFmFrequency(boolean isAm, int frequencyKhz) {
423        if (isAm) {
424            return frequencyKhz > 150 && frequencyKhz <= 30000;
425        } else {
426            return frequencyKhz > 60000 && frequencyKhz < 110000;
427        }
428    }
429
430    /**
431     * Builds new ProgramSelector for AM/FM frequency.
432     *
433     * This method variant supports HD Radio subchannels, but it's undesirable to
434     * select them manually. Instead, the value should be retrieved from program list.
435     *
436     * @param band the band.
437     * @param frequencyKhz the frequency in kHz.
438     * @param subChannel 1-based HD Radio subchannel.
439     * @return new ProgramSelector object representing given frequency.
440     * @throws IllegalArgumentException if provided frequency is out of bounds,
441     *         or tried setting a subchannel for analog AM/FM.
442     */
443    public static @NonNull ProgramSelector createAmFmSelector(
444            @RadioManager.Band int band, int frequencyKhz, int subChannel) {
445        if (band == RadioManager.BAND_INVALID) {
446            // 50MHz is a rough boundary between AM (<30MHz) and FM (>60MHz).
447            if (frequencyKhz < 50000) {
448                band = (subChannel <= 0) ? RadioManager.BAND_AM : RadioManager.BAND_AM_HD;
449            } else {
450                band = (subChannel <= 0) ? RadioManager.BAND_FM : RadioManager.BAND_FM_HD;
451            }
452        }
453
454        boolean isAm = (band == RadioManager.BAND_AM || band == RadioManager.BAND_AM_HD);
455        boolean isDigital = (band == RadioManager.BAND_AM_HD || band == RadioManager.BAND_FM_HD);
456        if (!isAm && !isDigital && band != RadioManager.BAND_FM) {
457            throw new IllegalArgumentException("Unknown band: " + band);
458        }
459        if (subChannel < 0 || subChannel > 8) {
460            throw new IllegalArgumentException("Invalid subchannel: " + subChannel);
461        }
462        if (subChannel > 0 && !isDigital) {
463            throw new IllegalArgumentException("Subchannels are not supported for non-HD radio");
464        }
465        if (!isValidAmFmFrequency(isAm, frequencyKhz)) {
466            throw new IllegalArgumentException("Provided value is not a valid AM/FM frequency: "
467                    + frequencyKhz);
468        }
469
470        // We can't use AM_HD or FM_HD, because we don't know HD station ID.
471        @ProgramType int programType = isAm ? PROGRAM_TYPE_AM : PROGRAM_TYPE_FM;
472        Identifier primary = new Identifier(IDENTIFIER_TYPE_AMFM_FREQUENCY, frequencyKhz);
473
474        Identifier[] secondary = null;
475        if (subChannel > 0) {
476            /* Stating sub channel for non-HD AM/FM does not give any guarantees,
477             * but we can't do much more without HD station ID.
478             *
479             * The legacy APIs had 1-based subChannels, while ProgramSelector is 0-based.
480             */
481            secondary = new Identifier[]{
482                    new Identifier(IDENTIFIER_TYPE_HD_SUBCHANNEL, subChannel - 1)};
483        }
484
485        return new ProgramSelector(programType, primary, secondary, null);
486    }
487
488    @Override
489    public String toString() {
490        StringBuilder sb = new StringBuilder("ProgramSelector(type=").append(mProgramType)
491                .append(", primary=").append(mPrimaryId);
492        if (mSecondaryIds.length > 0) sb.append(", secondary=").append(mSecondaryIds);
493        if (mVendorIds.length > 0) sb.append(", vendor=").append(mVendorIds);
494        sb.append(")");
495        return sb.toString();
496    }
497
498    @Override
499    public int hashCode() {
500        // secondaryIds and vendorIds are ignored for equality/hashing
501        return Objects.hash(mProgramType, mPrimaryId);
502    }
503
504    @Override
505    public boolean equals(Object obj) {
506        if (this == obj) return true;
507        if (!(obj instanceof ProgramSelector)) return false;
508        ProgramSelector other = (ProgramSelector) obj;
509        // secondaryIds and vendorIds are ignored for equality/hashing
510        return other.getProgramType() == mProgramType && mPrimaryId.equals(other.getPrimaryId());
511    }
512
513    private ProgramSelector(Parcel in) {
514        mProgramType = in.readInt();
515        mPrimaryId = in.readTypedObject(Identifier.CREATOR);
516        mSecondaryIds = in.createTypedArray(Identifier.CREATOR);
517        if (Stream.of(mSecondaryIds).anyMatch(id -> id == null)) {
518            throw new IllegalArgumentException("secondaryIds list must not contain nulls");
519        }
520        mVendorIds = in.createLongArray();
521    }
522
523    @Override
524    public void writeToParcel(Parcel dest, int flags) {
525        dest.writeInt(mProgramType);
526        dest.writeTypedObject(mPrimaryId, 0);
527        dest.writeTypedArray(mSecondaryIds, 0);
528        dest.writeLongArray(mVendorIds);
529    }
530
531    @Override
532    public int describeContents() {
533        return 0;
534    }
535
536    public static final Parcelable.Creator<ProgramSelector> CREATOR =
537            new Parcelable.Creator<ProgramSelector>() {
538        public ProgramSelector createFromParcel(Parcel in) {
539            return new ProgramSelector(in);
540        }
541
542        public ProgramSelector[] newArray(int size) {
543            return new ProgramSelector[size];
544        }
545    };
546
547    /**
548     * A single program identifier component, eg. frequency or channel ID.
549     *
550     * The long value field holds the value in format described in comments for
551     * IdentifierType constants.
552     */
553    public static final class Identifier implements Parcelable {
554        private final @IdentifierType int mType;
555        private final long mValue;
556
557        public Identifier(@IdentifierType int type, long value) {
558            if (type == IDENTIFIER_TYPE_HD_STATION_NAME) {
559                // see getType
560                type = IDENTIFIER_TYPE_HD_SUBCHANNEL;
561            }
562            mType = type;
563            mValue = value;
564        }
565
566        /**
567         * Type of an identifier.
568         *
569         * @return type of an identifier.
570         */
571        public @IdentifierType int getType() {
572            if (mType == IDENTIFIER_TYPE_HD_SUBCHANNEL && mValue > 10) {
573                /* HD_SUBCHANNEL and HD_STATION_NAME use the same identifier type, but they differ
574                 * in possible values: sub channel is 0-7, station name is greater than ASCII space
575                 * code (32).
576                 */
577                return IDENTIFIER_TYPE_HD_STATION_NAME;
578            }
579            return mType;
580        }
581
582        /**
583         * Value of an identifier.
584         *
585         * Its meaning depends on identifier type, ie. for IDENTIFIER_TYPE_AMFM_FREQUENCY type,
586         * the value is a frequency in kHz.
587         *
588         * The range of a value depends on its type; it does not always require the whole long
589         * range. Casting to necessary type (ie. int) without range checking is correct in front-end
590         * code - any range violations are either errors in the framework or in the
591         * HAL implementation. For example, IDENTIFIER_TYPE_AMFM_FREQUENCY always fits in int,
592         * as Integer.MAX_VALUE would mean 2.1THz.
593         *
594         * @return value of an identifier.
595         */
596        public long getValue() {
597            return mValue;
598        }
599
600        @Override
601        public String toString() {
602            return "Identifier(" + mType + ", " + mValue + ")";
603        }
604
605        @Override
606        public int hashCode() {
607            return Objects.hash(mType, mValue);
608        }
609
610        @Override
611        public boolean equals(Object obj) {
612            if (this == obj) return true;
613            if (!(obj instanceof Identifier)) return false;
614            Identifier other = (Identifier) obj;
615            return other.getType() == mType && other.getValue() == mValue;
616        }
617
618        private Identifier(Parcel in) {
619            mType = in.readInt();
620            mValue = in.readLong();
621        }
622
623        @Override
624        public void writeToParcel(Parcel dest, int flags) {
625            dest.writeInt(mType);
626            dest.writeLong(mValue);
627        }
628
629        @Override
630        public int describeContents() {
631            return 0;
632        }
633
634        public static final Parcelable.Creator<Identifier> CREATOR =
635                new Parcelable.Creator<Identifier>() {
636            public Identifier createFromParcel(Parcel in) {
637                return new Identifier(in);
638            }
639
640            public Identifier[] newArray(int size) {
641                return new Identifier[size];
642            }
643        };
644    }
645}
646